From 96974bd35651908bb69526971887c4348fee0615 Mon Sep 17 00:00:00 2001 From: tompro Date: Mon, 14 Apr 2025 19:12:37 +0200 Subject: [PATCH 1/8] sqldb: init This implements a basic driver for using Postgres, SQLite or MySQL as storage engine for nostr-database. Uses async Diesel as driver and Diesel migrations to set up the schema. Pull-Request: https://github.com/rust-nostr/nostr/pull/835 Pull-Request: https://github.com/rust-nostr/nostr/pull/855 Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1469 ++++++++++++++--- Cargo.toml | 1 + contrib/scripts/check-crates.sh | 3 + database/nostr-sqldb/Cargo.toml | 45 + database/nostr-sqldb/README.md | 25 + .../nostr-sqldb/examples/postgres-relay.rs | 31 + database/nostr-sqldb/migrations/.keep | 0 database/nostr-sqldb/migrations/mysql/.keep | 0 .../mysql/2025-04-11-095120_events/down.sql | 3 + .../mysql/2025-04-11-095120_events/up.sql | 26 + .../down.sql | 6 + .../up.sql | 36 + .../2025-04-11-095120_events/down.sql | 3 + .../postgres/2025-04-11-095120_events/up.sql | 26 + database/nostr-sqldb/migrations/sqlite/.keep | 0 .../sqlite/2025-04-11-095120_events/down.sql | 3 + .../sqlite/2025-04-11-095120_events/up.sql | 26 + database/nostr-sqldb/mysql.toml | 9 + database/nostr-sqldb/postgres.toml | 9 + database/nostr-sqldb/sqlite.toml | 9 + database/nostr-sqldb/src/lib.rs | 17 + database/nostr-sqldb/src/migrations/mod.rs | 8 + database/nostr-sqldb/src/migrations/mysql.rs | 19 + .../nostr-sqldb/src/migrations/postgres.rs | 19 + database/nostr-sqldb/src/migrations/sqlite.rs | 19 + database/nostr-sqldb/src/model.rs | 86 + database/nostr-sqldb/src/postgres.rs | 235 +++ database/nostr-sqldb/src/query.rs | 126 ++ database/nostr-sqldb/src/schema/mod.rs | 8 + database/nostr-sqldb/src/schema/mysql.rs | 29 + database/nostr-sqldb/src/schema/postgres.rs | 24 + database/nostr-sqldb/src/schema/sqlite.rs | 24 + 32 files changed, 2092 insertions(+), 252 deletions(-) create mode 100644 database/nostr-sqldb/Cargo.toml create mode 100644 database/nostr-sqldb/README.md create mode 100644 database/nostr-sqldb/examples/postgres-relay.rs create mode 100644 database/nostr-sqldb/migrations/.keep create mode 100644 database/nostr-sqldb/migrations/mysql/.keep create mode 100644 database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/down.sql create mode 100644 database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/up.sql create mode 100644 database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/down.sql create mode 100644 database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/up.sql create mode 100644 database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/down.sql create mode 100644 database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/up.sql create mode 100644 database/nostr-sqldb/migrations/sqlite/.keep create mode 100644 database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/down.sql create mode 100644 database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/up.sql create mode 100644 database/nostr-sqldb/mysql.toml create mode 100644 database/nostr-sqldb/postgres.toml create mode 100644 database/nostr-sqldb/sqlite.toml create mode 100644 database/nostr-sqldb/src/lib.rs create mode 100644 database/nostr-sqldb/src/migrations/mod.rs create mode 100644 database/nostr-sqldb/src/migrations/mysql.rs create mode 100644 database/nostr-sqldb/src/migrations/postgres.rs create mode 100644 database/nostr-sqldb/src/migrations/sqlite.rs create mode 100644 database/nostr-sqldb/src/model.rs create mode 100644 database/nostr-sqldb/src/postgres.rs create mode 100644 database/nostr-sqldb/src/query.rs create mode 100644 database/nostr-sqldb/src/schema/mod.rs create mode 100644 database/nostr-sqldb/src/schema/mysql.rs create mode 100644 database/nostr-sqldb/src/schema/postgres.rs create mode 100644 database/nostr-sqldb/src/schema/sqlite.rs diff --git a/Cargo.lock b/Cargo.lock index 4c73c8088..b42d4dc5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.11" @@ -60,7 +74,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -72,6 +86,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "amplify" version = "4.6.1" @@ -206,7 +226,7 @@ dependencies = [ "libc", "once_cell", "postage", - "rand", + "rand 0.8.5", "safelog", "serde", "thiserror 2.0.8", @@ -577,6 +597,15 @@ dependencies = [ "serde", ] +[[package]] +name = "btoi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" +dependencies = [ + "num-traits", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -603,9 +632,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "caret" @@ -628,6 +657,8 @@ version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -652,6 +683,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.1" @@ -751,6 +788,24 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "coarsetime" version = "0.1.34" @@ -793,10 +848,10 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ - "encode_unicode", + "encode_unicode 0.3.6", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.14", "windows-sys 0.52.0", ] @@ -877,6 +932,47 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -899,7 +995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -911,10 +1007,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "ctr" version = "0.9.2" @@ -1125,7 +1242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b84d32b18d9a256d81e4fec2e4cfd0ab6dde5e5ff49be1713ae0adbd0060c2" dependencies = [ "heck 0.5.0", - "indexmap 2.12.0", + "indexmap 2.5.0", "itertools 0.12.1", "proc-macro-crate", "proc-macro2", @@ -1143,7 +1260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b48e8e38a4aa565da767322b5ca55fb0f8347983c5bc7f7647db069405420479" dependencies = [ "heck 0.5.0", - "indexmap 2.12.0", + "indexmap 2.5.0", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", @@ -1242,6 +1359,76 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diesel" +version = "2.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d3950690ba3a6910126162b47e775e203006d4242a15de912bec6c0a695153" +dependencies = [ + "bitflags 2.9.0", + "byteorder", + "diesel_derives", + "itoa", + "libsqlite3-sys", + "mysqlclient-sys", + "percent-encoding", + "pq-sys", + "serde_json", + "time", + "url", +] + +[[package]] +name = "diesel-async" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51a307ac00f7c23f526a04a77761a0519b9f0eb2838ebf5b905a58580095bdcb" +dependencies = [ + "async-trait", + "deadpool", + "diesel", + "futures-channel", + "futures-util", + "mysql_async", + "mysql_common", + "scoped-futures", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "diesel_derives" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93958254b70bea63b4187ff73d10180599d9d8d177071b7f91e6da4e0c0ad55" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "diesel_migrations" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn 2.0.90", +] + [[package]] name = "digest" version = "0.10.7" @@ -1281,6 +1468,16 @@ dependencies = [ "dirs-sys 0.5.0", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.4.1" @@ -1305,6 +1502,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1331,6 +1539,20 @@ dependencies = [ "phf", ] +[[package]] +name = "dsl_auto_type" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" +dependencies = [ + "darling 0.20.10", + "either", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "dyn-clone" version = "1.0.17" @@ -1370,7 +1592,7 @@ dependencies = [ "curve25519-dalek", "ed25519", "merlin", - "rand_core", + "rand_core 0.6.4", "serde", "sha2", "subtle", @@ -1407,8 +1629,10 @@ dependencies = [ "ff", "generic-array", "group", + "hkdf", + "pem-rfc7468", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1420,6 +1644,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "enum-ordinalize" version = "3.1.15" @@ -1462,6 +1692,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + [[package]] name = "event-listener" version = "5.3.1" @@ -1473,6 +1709,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1503,13 +1745,24 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fd-lock" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" +dependencies = [ + "cfg-if", + "rustix 0.38.44", + "windows-sys 0.52.0", +] + [[package]] name = "ff" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1580,24 +1833,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "749cff877dc1af878a0b31a41dd221a753634401ea0ef2f87b62d3171522485a" -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "spin 0.9.8", -] - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1796,6 +2043,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.0" @@ -1833,7 +2090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1866,9 +2123,14 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hashlink" @@ -1985,6 +2247,46 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" +[[package]] +name = "hpke-rs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11bd4ee27b79fa1820e72ef8489cc729c87299ec3f7f52b8fc8dcb87cb2d485" +dependencies = [ + "hpke-rs-crypto", + "log", + "serde", + "tls_codec", + "zeroize", +] + +[[package]] +name = "hpke-rs-crypto" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c3f1ae0a26c18d6469a70db1217136056261c4a244b09a755bc60bd4e055b67" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "hpke-rs-rust-crypto" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08d4500baf0aced746723d3515d08212bdb9d941df6d1aca3d46d1619b2a1cf" +dependencies = [ + "aes-gcm", + "chacha20poly1305", + "hkdf", + "hpke-rs-crypto", + "p256", + "p384", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "sha2", + "x25519-dalek", +] + [[package]] name = "http" version = "1.1.0" @@ -2008,12 +2310,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http", "http-body", "pin-project-lite", @@ -2082,16 +2384,15 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 0.26.6", + "webpki-roots", ] [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" dependencies = [ - "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -2099,9 +2400,7 @@ dependencies = [ "http", "http-body", "hyper", - "ipnet", "libc", - "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -2178,14 +2477,26 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.14.5", "serde", - "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width 0.2.0", + "web-time", ] [[package]] @@ -2236,17 +2547,6 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" -[[package]] -name = "io-uring" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags 2.9.0", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.10.0" @@ -2254,20 +2554,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] -name = "iri-string" -version = "0.7.8" +name = "is-terminal" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi 0.4.0", "libc", @@ -2326,6 +2616,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -2355,6 +2654,15 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keyed_priority_queue" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" +dependencies = [ + "indexmap 2.5.0", +] + [[package]] name = "keyring" version = "3.6.2" @@ -2509,9 +2817,18 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.16.0" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "lru" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ea4e65087ff52f3862caff188d489f1fab49a0cb09e01b2e3f1a617b10aaed" +checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198" [[package]] name = "macroific" @@ -2569,6 +2886,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2592,10 +2919,31 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core", + "rand_core 0.6.4", "zeroize", ] +[[package]] +name = "migrations_internals" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "migrations_macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" +dependencies = [ + "migrations_internals", + "proc-macro2", + "quote", +] + [[package]] name = "mime" version = "0.3.17" @@ -2641,12 +2989,75 @@ dependencies = [ ] [[package]] -name = "nanorand" -version = "0.7.0" +name = "mysql_async" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +checksum = "a0b66e411c31265e879d9814d03721f2daa7ad07337b6308cb4bb0cde7e6fd47" dependencies = [ - "getrandom 0.2.15", + "bytes", + "crossbeam", + "flate2", + "futures-core", + "futures-sink", + "futures-util", + "keyed_priority_queue", + "lru 0.12.5", + "mysql_common", + "pem", + "percent-encoding", + "pin-project", + "rand 0.8.5", + "serde", + "serde_json", + "socket2", + "thiserror 1.0.64", + "tokio", + "tokio-util", + "twox-hash", + "url", +] + +[[package]] +name = "mysql_common" +version = "0.32.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478b0ff3f7d67b79da2b96f56f334431aef65e15ba4b29dd74a4236e29582bdc" +dependencies = [ + "base64 0.21.7", + "bindgen", + "bitflags 2.9.0", + "btoi", + "byteorder", + "bytes", + "cc", + "cmake", + "crc32fast", + "flate2", + "lazy_static", + "num-bigint", + "num-traits", + "rand 0.8.5", + "regex", + "saturating", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "subprocess", + "thiserror 1.0.64", + "uuid", + "zstd", +] + +[[package]] +name = "mysqlclient-sys" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29e21174d84e2622ceb7b0146a9187d36458a3a9ee9a66c9cac22e96493ef9" +dependencies = [ + "pkg-config", + "vcpkg", ] [[package]] @@ -2661,6 +3072,29 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" +[[package]] +name = "nip07" +version = "0.42.0" +dependencies = [ + "js-sys", + "nostr", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -2673,7 +3107,7 @@ dependencies = [ [[package]] name = "nostr" -version = "0.44.1" +version = "0.42.1" dependencies = [ "aes", "base64 0.22.1", @@ -2684,7 +3118,6 @@ dependencies = [ "chacha20", "chacha20poly1305", "getrandom 0.2.15", - "hex", "instant", "nostr-ots", "reqwest", @@ -2700,7 +3133,7 @@ dependencies = [ [[package]] name = "nostr-blossom" -version = "0.44.0" +version = "0.42.0" dependencies = [ "base64 0.22.1", "clap", @@ -2711,37 +3144,26 @@ dependencies = [ ] [[package]] -name = "nostr-browser-signer" -version = "0.44.1" -dependencies = [ - "js-sys", - "nostr", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "nostr-browser-signer-proxy" -version = "0.44.0" +name = "nostr-cli" +version = "0.42.0" dependencies = [ - "atomic-destructor", - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "nostr", - "serde", - "serde_json", + "clap", + "dialoguer", + "dirs 5.0.1", + "indicatif", + "nostr-connect", + "nostr-relay-builder", + "nostr-sdk", + "once_cell", + "prettytable-rs", + "regex", + "rustyline", "tokio", - "tracing", - "tracing-subscriber", - "uuid", ] [[package]] name = "nostr-connect" -version = "0.44.0" +version = "0.42.0" dependencies = [ "async-utility", "dialoguer", @@ -2755,7 +3177,7 @@ dependencies = [ [[package]] name = "nostr-database" -version = "0.44.0" +version = "0.42.0" dependencies = [ "flatbuffers 25.2.10", "lru", @@ -2800,7 +3222,7 @@ dependencies = [ [[package]] name = "nostr-http-file-storage" -version = "0.44.0" +version = "0.42.0" dependencies = [ "nostr", "reqwest", @@ -2809,9 +3231,8 @@ dependencies = [ [[package]] name = "nostr-indexeddb" -version = "0.44.0" +version = "0.42.0" dependencies = [ - "hex", "indexed_db_futures", "nostr", "nostr-database", @@ -2819,7 +3240,7 @@ dependencies = [ [[package]] name = "nostr-keyring" -version = "0.44.1" +version = "0.42.0" dependencies = [ "async-utility", "keyring", @@ -2829,11 +3250,9 @@ dependencies = [ [[package]] name = "nostr-lmdb" -version = "0.44.0" +version = "0.42.0" dependencies = [ "async-utility", - "flume", - "futures", "heed", "nostr", "nostr-database", @@ -2843,11 +3262,71 @@ dependencies = [ "tracing", ] +[[package]] +name = "nostr-mls" +version = "0.42.0" +dependencies = [ + "aes-gcm", + "nostr", + "nostr-mls-memory-storage", + "nostr-mls-sqlite-storage", + "nostr-mls-storage", + "openmls", + "openmls_basic_credential", + "openmls_rust_crypto", + "openmls_traits", + "tempfile", + "tls_codec", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "nostr-mls-memory-storage" +version = "0.42.0" +dependencies = [ + "aes-gcm", + "lru", + "nostr", + "nostr-mls-storage", + "openmls", + "openmls_memory_storage", + "parking_lot", +] + +[[package]] +name = "nostr-mls-sqlite-storage" +version = "0.42.0" +dependencies = [ + "aes-gcm", + "nostr", + "nostr-mls-storage", + "openmls", + "openmls_sqlite_storage", + "refinery", + "rusqlite", + "serde", + "serde_json", + "tempfile", + "tracing", +] + +[[package]] +name = "nostr-mls-storage" +version = "0.42.0" +dependencies = [ + "nostr", + "openmls", + "openmls_traits", + "serde", + "serde_json", +] + [[package]] name = "nostr-ndb" -version = "0.44.0" +version = "0.42.0" dependencies = [ - "hex", "nostr", "nostr-database", "nostrdb", @@ -2868,13 +3347,12 @@ dependencies = [ [[package]] name = "nostr-relay-builder" -version = "0.44.0" +version = "0.42.0" dependencies = [ "async-utility", "async-wsocket", "atomic-destructor", "base64 0.22.1", - "hex", "hyper", "hyper-util", "negentropy", @@ -2887,13 +3365,12 @@ dependencies = [ [[package]] name = "nostr-relay-pool" -version = "0.44.0" +version = "0.42.0" dependencies = [ "async-utility", "async-wsocket", "atomic-destructor", - "hex", - "lru", + "lru 0.14.0", "negentropy", "nostr", "nostr-database", @@ -2905,14 +3382,13 @@ dependencies = [ [[package]] name = "nostr-sdk" -version = "0.44.1" +version = "0.42.0" dependencies = [ "async-utility", "nostr", "nostr-connect", "nostr-database", - "nostr-gossip", - "nostr-gossip-memory", + "nostr-indexeddb", "nostr-lmdb", "nostr-ndb", "nostr-relay-pool", @@ -2921,11 +3397,27 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "nostr-sqldb" +version = "0.41.0" +dependencies = [ + "deadpool", + "diesel", + "diesel-async", + "diesel_migrations", + "nostr", + "nostr-database", + "nostr-relay-builder", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "nostrdb" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195bddbd8f436caa9fe3edbcba5743a4386a7ab03b1b81b59abf5b42262edfe5" +checksum = "df9b01d00781031ad2d6837a178e0e6ec343ff4ffc36a4510a583ed6cc67e159" dependencies = [ "bindgen", "cc", @@ -3007,7 +3499,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -3068,6 +3560,16 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + [[package]] name = "num_enum" version = "0.7.3" @@ -3089,10 +3591,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "nwc" -version = "0.44.0" +version = "0.42.0" dependencies = [ + "async-utility", "nostr", "nostr-relay-pool", "tokio", @@ -3155,6 +3664,90 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openmls" +version = "0.6.1" +source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +dependencies = [ + "log", + "openmls_traits", + "rayon", + "serde", + "thiserror 2.0.8", + "tls_codec", +] + +[[package]] +name = "openmls_basic_credential" +version = "0.3.0" +source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +dependencies = [ + "ed25519-dalek", + "openmls_traits", + "p256", + "rand 0.8.5", + "serde", + "tls_codec", +] + +[[package]] +name = "openmls_memory_storage" +version = "0.3.0" +source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +dependencies = [ + "log", + "openmls_traits", + "serde", + "serde_json", + "thiserror 2.0.8", +] + +[[package]] +name = "openmls_rust_crypto" +version = "0.3.0" +source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +dependencies = [ + "aes-gcm", + "chacha20poly1305", + "ed25519-dalek", + "hkdf", + "hmac", + "hpke-rs", + "hpke-rs-crypto", + "hpke-rs-rust-crypto", + "openmls_memory_storage", + "openmls_traits", + "p256", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "sha2", + "thiserror 2.0.8", + "tls_codec", +] + +[[package]] +name = "openmls_sqlite_storage" +version = "0.1.0" +source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +dependencies = [ + "log", + "openmls_traits", + "refinery", + "rusqlite", + "serde", + "thiserror 1.0.64", +] + +[[package]] +name = "openmls_traits" +version = "0.3.0" +source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +dependencies = [ + "serde", + "tls_codec", +] + [[package]] name = "opentimestamps" version = "0.2.0" @@ -3230,7 +3823,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "rand_core", + "rand_core 0.6.4", "sha2", ] @@ -3280,7 +3873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -3300,6 +3893,16 @@ dependencies = [ "hmac", ] +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3332,7 +3935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", ] [[package]] @@ -3354,7 +3957,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] @@ -3427,6 +4030,24 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "postage" version = "0.5.0" @@ -3442,6 +4063,35 @@ dependencies = [ "thiserror 1.0.64", ] +[[package]] +name = "postgres-protocol" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" +dependencies = [ + "base64 0.22.1", + "byteorder", + "bytes", + "fallible-iterator 0.2.0", + "hmac", + "md-5", + "memchr", + "rand 0.9.0", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" +dependencies = [ + "bytes", + "fallible-iterator 0.2.0", + "postgres-protocol", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3454,7 +4104,17 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", +] + +[[package]] +name = "pq-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c852911b98f5981956037b2ca976660612e548986c30af075e753107bc3400" +dependencies = [ + "libc", + "vcpkg", ] [[package]] @@ -3467,6 +4127,20 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "prettytable-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" +dependencies = [ + "csv", + "encode_unicode 1.0.0", + "is-terminal", + "lazy_static", + "term", + "unicode-width 0.1.14", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -3484,7 +4158,7 @@ checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", - "indexmap 2.12.0", + "indexmap 2.5.0", ] [[package]] @@ -3542,7 +4216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", - "rand", + "rand 0.8.5", "ring 0.17.14", "rustc-hash 2.0.0", "rustls", @@ -3587,8 +4261,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.24", ] [[package]] @@ -3598,7 +4283,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -3610,12 +4305,41 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] + [[package]] name = "rangemap" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.5" @@ -3647,6 +4371,50 @@ dependencies = [ "thiserror 2.0.8", ] +[[package]] +name = "refinery" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba5d693abf62492c37268512ff35b77655d2e957ca53dab85bf993fe9172d15" +dependencies = [ + "refinery-core", + "refinery-macros", +] + +[[package]] +name = "refinery-core" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a83581f18c1a4c3a6ebd7a174bdc665f17f618d79f7edccb6a0ac67e660b319" +dependencies = [ + "async-trait", + "cfg-if", + "log", + "regex", + "rusqlite", + "serde", + "siphasher 1.0.1", + "thiserror 1.0.64", + "time", + "toml", + "url", + "walkdir", +] + +[[package]] +name = "refinery-macros" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c225407d8e52ef8cf094393781ecda9a99d6544ec28d90a6915751de259264" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "refinery-core", + "regex", + "syn 2.0.90", +] + [[package]] name = "regex" version = "1.11.1" @@ -3693,9 +4461,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", @@ -3707,13 +4475,17 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", + "ipnet", "js-sys", "log", + "mime", "mime_guess", + "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -3721,14 +4493,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tower", - "tower-http", + "tokio-socks", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.1", + "webpki-roots", + "windows-registry", ] [[package]] @@ -3789,7 +4561,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sha2", "signature", "spki", @@ -3804,7 +4576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ "bitflags 2.9.0", - "fallible-iterator", + "fallible-iterator 0.3.0", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", @@ -3884,9 +4656,19 @@ dependencies = [ "once_cell", "ring 0.17.14", "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", ] [[package]] @@ -3912,6 +4694,26 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "rustyline" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fd9ca5ebc709e8535e8ef7c658eb51457987e48c98ead2be482172accc408d" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "clipboard-win", + "fd-lock", + "libc", + "log", + "memchr", + "nix", + "unicode-segmentation", + "unicode-width 0.2.0", + "utf8parse", + "windows-sys 0.59.0", +] + [[package]] name = "ryu" version = "1.0.18" @@ -3958,6 +4760,21 @@ dependencies = [ "regex", ] +[[package]] +name = "saturating" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" + +[[package]] +name = "scoped-futures" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b24aae2d0636530f359e9d5ef0c04669d11c5e756699b27a6a6d845d8329091" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3996,7 +4813,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -4054,11 +4871,10 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ - "serde_core", "serde_derive", ] @@ -4081,20 +4897,11 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -4112,9 +4919,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -4153,7 +4960,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", + "indexmap 2.5.0", "serde", "serde_derive", "serde_json", @@ -4253,7 +5060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -4262,6 +5069,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -4321,9 +5134,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spki" @@ -4365,7 +5175,7 @@ dependencies = [ "p256", "p384", "p521", - "rand_core", + "rand_core 0.6.4", "rsa", "sec1", "sha2", @@ -4382,6 +5192,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.10.0" @@ -4416,6 +5237,16 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "subprocess" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4481,9 +5312,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", "getrandom 0.3.1", @@ -4492,6 +5323,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -4606,21 +5448,41 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "serde", + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "tokio" -version = "1.46.1" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -4637,6 +5499,32 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tokio-postgres" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator 0.2.0", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.9.0", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-rustls" version = "0.26.0" @@ -4673,7 +5561,7 @@ dependencies = [ "tokio", "tokio-rustls", "tungstenite", - "webpki-roots 0.26.6", + "webpki-roots", ] [[package]] @@ -4717,7 +5605,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", @@ -4751,8 +5639,8 @@ dependencies = [ "itertools 0.14.0", "libc", "paste", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "slab", "smallvec", @@ -4791,7 +5679,7 @@ dependencies = [ "derive_more 2.0.1", "educe", "paste", - "rand", + "rand 0.8.5", "smallvec", "thiserror 2.0.8", "tor-basic-utils", @@ -4836,7 +5724,7 @@ dependencies = [ "futures", "oneshot-fused-workaround", "postage", - "rand", + "rand 0.8.5", "safelog", "serde", "thiserror 2.0.8", @@ -4890,7 +5778,7 @@ dependencies = [ "once_cell", "oneshot-fused-workaround", "pin-project", - "rand", + "rand 0.8.5", "retry-error", "safelog", "serde", @@ -5031,7 +5919,7 @@ dependencies = [ "oneshot-fused-workaround", "paste", "postage", - "rand", + "rand 0.8.5", "rusqlite", "safelog", "scopeguard", @@ -5108,7 +5996,7 @@ dependencies = [ "oneshot-fused-workaround", "pin-project", "postage", - "rand", + "rand 0.8.5", "safelog", "serde", "strum", @@ -5144,7 +6032,7 @@ dependencies = [ "itertools 0.14.0", "oneshot-fused-workaround", "postage", - "rand", + "rand 0.8.5", "retry-error", "safelog", "slotmap-careful", @@ -5185,7 +6073,7 @@ dependencies = [ "digest", "itertools 0.14.0", "paste", - "rand", + "rand 0.8.5", "safelog", "signature", "subtle", @@ -5253,8 +6141,8 @@ dependencies = [ "once_cell", "oneshot-fused-workaround", "postage", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "retry-error", "safelog", "serde", @@ -5296,7 +6184,7 @@ dependencies = [ "derive_more 2.0.1", "downcast-rs", "paste", - "rand", + "rand 0.8.5", "signature", "ssh-key", "thiserror 2.0.8", @@ -5326,7 +6214,7 @@ dependencies = [ "humantime", "inventory", "itertools 0.14.0", - "rand", + "rand 0.8.5", "serde", "signature", "ssh-key", @@ -5390,7 +6278,7 @@ dependencies = [ "educe", "getrandom 0.2.15", "hex", - "rand_core", + "rand_core 0.6.4", "rsa", "safelog", "serde", @@ -5465,7 +6353,7 @@ dependencies = [ "humantime", "itertools 0.14.0", "num_enum", - "rand", + "rand 0.8.5", "serde", "static_assertions", "strum", @@ -5502,7 +6390,7 @@ dependencies = [ "itertools 0.14.0", "once_cell", "phf", - "rand", + "rand 0.8.5", "serde", "serde_with", "signature", @@ -5580,8 +6468,8 @@ dependencies = [ "hmac", "oneshot-fused-workaround", "pin-project", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "safelog", "slotmap-careful", "static_assertions", @@ -5628,7 +6516,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e41754428684bd62892df2c74c2d11128cfbf3f1a8a9aaa1b920fcb90e04961a" dependencies = [ - "rand", + "rand 0.8.5", "serde", "tor-basic-utils", "tor-linkspec", @@ -5722,45 +6610,6 @@ dependencies = [ "tor-memquota", ] -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags 2.9.0", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - [[package]] name = "tower-service" version = "0.3.3" @@ -5867,7 +6716,7 @@ dependencies = [ "http", "httparse", "log", - "rand", + "rand 0.8.5", "rustls", "rustls-pki-types", "sha1", @@ -5875,6 +6724,17 @@ dependencies = [ "utf-8", ] +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "rand 0.8.5", + "static_assertions", +] + [[package]] name = "typed-index-collections" version = "3.1.0" @@ -5923,6 +6783,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -5935,6 +6801,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -5976,7 +6848,7 @@ dependencies = [ "rustls", "rustls-pki-types", "url", - "webpki-roots 0.26.6", + "webpki-roots", ] [[package]] @@ -6017,13 +6889,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ - "getrandom 0.3.1", - "js-sys", - "serde", + "getrandom 0.2.15", "wasm-bindgen", ] @@ -6096,6 +6966,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasix" version = "0.12.21" @@ -6133,13 +7009,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", "web-sys", ] @@ -6192,6 +7067,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webbrowser" version = "1.0.4" @@ -6218,15 +7103,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "which" version = "4.4.2" @@ -6239,6 +7115,17 @@ dependencies = [ "rustix 0.38.44", ] +[[package]] +name = "whoami" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -6279,6 +7166,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -6527,7 +7444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "serde", "zeroize", ] @@ -6555,7 +7472,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -6569,6 +7495,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -6588,3 +7525,31 @@ dependencies = [ "quote", "syn 2.0.90", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index e1fc20cb7..495fc59f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "database/nostr-indexeddb", "database/nostr-lmdb", "database/nostr-ndb", + "database/nostr-sqldb", # Gossip "gossip/nostr-gossip", diff --git a/contrib/scripts/check-crates.sh b/contrib/scripts/check-crates.sh index 5cf7b2578..6344ca4d8 100755 --- a/contrib/scripts/check-crates.sh +++ b/contrib/scripts/check-crates.sh @@ -39,6 +39,9 @@ buildargs=( "-p nostr-gossip-memory" "-p nostr-gossip-test-suite" "-p nostr-lmdb" + "-p nostr-sqldb --no-default-features --features postgres" # PostgreSQL + "-p nostr-sqldb --no-default-features --features mysql" # MySQL + "-p nostr-sqldb --no-default-features --features sqlite" # SQLite "-p nostr-indexeddb --target wasm32-unknown-unknown" "-p nostr-ndb" "-p nostr-keyring" diff --git a/database/nostr-sqldb/Cargo.toml b/database/nostr-sqldb/Cargo.toml new file mode 100644 index 000000000..00c65bc3f --- /dev/null +++ b/database/nostr-sqldb/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "nostr-sqldb" +version = "0.42.0" +edition = "2021" +description = "SQL storage backend for Nostr apps" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +readme = "README.md" +rust-version.workspace = true +keywords = ["nostr", "database", "postgres", "mysql", "sqlite"] + +[features] +default = ["postgres"] +postgres = [ + "diesel/postgres", + "diesel-async/postgres", + "diesel_migrations/postgres", +] +mysql = ["diesel/mysql", "diesel-async/mysql", "diesel_migrations/mysql"] +sqlite = [ + "diesel/sqlite", + "diesel-async/sqlite", + "diesel_migrations/sqlite", + "diesel/returning_clauses_for_sqlite_3_35", +] + +[dependencies] +deadpool = { version = "0.12", features = ["managed", "rt_tokio_1"] } +diesel = { version = "2.2", features = ["serde_json"] } +diesel-async = { version = "0.5", features = ["deadpool"] } +diesel_migrations = "2.2" +nostr = { workspace = true, features = ["std"] } +nostr-database = { workspace = true, features = ["flatbuf"] } +tracing.workspace = true + +[dev-dependencies] +nostr-relay-builder.workspace = true +tokio.workspace = true +tracing-subscriber.workspace = true + +[[example]] +name = "postgres-relay" +required-features = ["postgres"] diff --git a/database/nostr-sqldb/README.md b/database/nostr-sqldb/README.md new file mode 100644 index 000000000..b2641b6a1 --- /dev/null +++ b/database/nostr-sqldb/README.md @@ -0,0 +1,25 @@ +# Nostr SQL database backend + +SQL storage backend for nostr apps working with Postgres, SQLite and MySQL. + +## Crate Feature Flags + +The following crate feature flags are available: + +| Feature | Default | Description | +|-------------|:-------:|-------------------------------| +| `postgres` | Yes | Enable support for PostgreSQL | +| `mysql` | No | Enable support for MySQL | +| `sqlite` | No | Enable support for SQLite | + +## State + +**This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. + +## Donations + +`rust-nostr` is free and open-source. This means we do not earn any revenue by selling it. Instead, we rely on your financial support. If you actively use any of the `rust-nostr` libs/software/services, then please [donate](https://rust-nostr.org/donate). + +## License + +This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details diff --git a/database/nostr-sqldb/examples/postgres-relay.rs b/database/nostr-sqldb/examples/postgres-relay.rs new file mode 100644 index 000000000..72fbfef91 --- /dev/null +++ b/database/nostr-sqldb/examples/postgres-relay.rs @@ -0,0 +1,31 @@ +// Copyright (c) 2025 Protom +// Distributed under the MIT software license + +use std::time::Duration; + +use nostr_database::prelude::*; +use nostr_relay_builder::prelude::*; +use nostr_sqldb::NostrPostgres; + +// Your database URL +const DB_URL: &str = "postgres://postgres:password@localhost:5432"; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + // Create a nostr db instance and run pending db migrations if any + let db = NostrPostgres::new(DB_URL).await?; + + // Add db to builder + let builder = RelayBuilder::default().database(db); + + // Create local relay + let relay = LocalRelay::run(builder).await?; + println!("Url: {}", relay.url()); + + // Keep up the program + loop { + tokio::time::sleep(Duration::from_secs(60)).await; + } +} diff --git a/database/nostr-sqldb/migrations/.keep b/database/nostr-sqldb/migrations/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/database/nostr-sqldb/migrations/mysql/.keep b/database/nostr-sqldb/migrations/mysql/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/down.sql b/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/down.sql new file mode 100644 index 000000000..01cb13d3d --- /dev/null +++ b/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS event_tags; +DROP TABLE IF EXISTS events; diff --git a/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/up.sql b/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/up.sql new file mode 100644 index 000000000..ae6d82518 --- /dev/null +++ b/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/up.sql @@ -0,0 +1,26 @@ +-- The actual event data +CREATE TABLE IF NOT EXISTS events ( + id BLOB(32) PRIMARY KEY NOT NULL, + pubkey BLOB(32) NOT NULL, + created_at BIGINT NOT NULL, + kind BIGINT NOT NULL, + payload BLOB NOT NULL, + deleted BOOLEAN NOT NULL +); + +-- Direct indexes +CREATE INDEX event_pubkey ON events (pubkey); +CREATE INDEX event_date ON events (created_at); +CREATE INDEX event_kind ON events (kind); +CREATE INDEX event_deleted ON events (deleted); + +-- The tag index, the primary will give us the index automatically +CREATE TABLE IF NOT EXISTS event_tags ( + tag VARCHAR(64) NOT NULL, + tag_value VARCHAR(512) NOT NULL, + event_id BLOB(32) NOT NULL + REFERENCES events (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + PRIMARY KEY (tag, tag_value, event_id) +); diff --git a/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/down.sql b/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 000000000..a9f526091 --- /dev/null +++ b/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/up.sql b/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 000000000..d68895b1a --- /dev/null +++ b/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/down.sql b/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/down.sql new file mode 100644 index 000000000..ab16f6bdf --- /dev/null +++ b/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP TABLE event_tags; +DROP TABLE events; diff --git a/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/up.sql b/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/up.sql new file mode 100644 index 000000000..74f300294 --- /dev/null +++ b/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/up.sql @@ -0,0 +1,26 @@ +-- The actual event data +CREATE TABLE events ( + id BYTEA PRIMARY KEY NOT NULL, + pubkey BYTEA NOT NULL, + created_at BIGINT NOT NULL, + kind BIGINT NOT NULL, + payload BYTEA NOT NULL, + deleted BOOLEAN NOT NULL +); + +-- Direct indexes +CREATE INDEX event_pubkey ON events (pubkey); +CREATE INDEX event_date ON events (created_at); +CREATE INDEX event_kind ON events (kind); +CREATE INDEX event_deleted ON events (deleted); + +-- The tag index, the primary will give us the index automatically +CREATE TABLE event_tags ( + tag TEXT NOT NULL, + tag_value TEXT NOT NULL, + event_id BYTEA NOT NULL + REFERENCES events (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + PRIMARY KEY (tag, tag_value, event_id) +); diff --git a/database/nostr-sqldb/migrations/sqlite/.keep b/database/nostr-sqldb/migrations/sqlite/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/down.sql b/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/down.sql new file mode 100644 index 000000000..ab16f6bdf --- /dev/null +++ b/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP TABLE event_tags; +DROP TABLE events; diff --git a/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/up.sql b/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/up.sql new file mode 100644 index 000000000..8d1f54314 --- /dev/null +++ b/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/up.sql @@ -0,0 +1,26 @@ +-- The actual event data +CREATE TABLE events ( + id BLOB PRIMARY KEY NOT NULL, + pubkey BLOB NOT NULL, + created_at BIGINT NOT NULL, + kind BIGINT NOT NULL, + payload BLOB NOT NULL, + deleted BOOLEAN NOT NULL +); + +-- Direct indexes +CREATE INDEX event_pubkey ON events (pubkey); +CREATE INDEX event_date ON events (created_at); +CREATE INDEX event_kind ON events (kind); +CREATE INDEX event_deleted ON events (deleted); + +-- The tag index, the primary will give us the index automatically +CREATE TABLE event_tags ( + tag TEXT NOT NULL, + tag_value TEXT NOT NULL, + event_id BLOB NOT NULL + REFERENCES events (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + PRIMARY KEY (tag, tag_value, event_id) +); diff --git a/database/nostr-sqldb/mysql.toml b/database/nostr-sqldb/mysql.toml new file mode 100644 index 000000000..75175050b --- /dev/null +++ b/database/nostr-sqldb/mysql.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema/mysql.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "migrations/mysql" diff --git a/database/nostr-sqldb/postgres.toml b/database/nostr-sqldb/postgres.toml new file mode 100644 index 000000000..285cb79ad --- /dev/null +++ b/database/nostr-sqldb/postgres.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema/postgres.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "migrations/postgres" diff --git a/database/nostr-sqldb/sqlite.toml b/database/nostr-sqldb/sqlite.toml new file mode 100644 index 000000000..e1c7f21c0 --- /dev/null +++ b/database/nostr-sqldb/sqlite.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema/sqlite.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "migrations/sqlite" diff --git a/database/nostr-sqldb/src/lib.rs b/database/nostr-sqldb/src/lib.rs new file mode 100644 index 000000000..a1c3456fd --- /dev/null +++ b/database/nostr-sqldb/src/lib.rs @@ -0,0 +1,17 @@ +mod migrations; +#[allow(dead_code)] // TODO: to remove when also SQLite and MySQL are implemented +mod model; +#[cfg(feature = "postgres")] +mod postgres; +#[allow(dead_code)] // TODO: to remove when also SQLite and MySQL are implemented +mod query; +mod schema; + +#[cfg(feature = "mysql")] +pub use migrations::mysql::run_migrations; +#[cfg(feature = "postgres")] +pub use migrations::postgres::run_migrations; +#[cfg(feature = "sqlite")] +pub use migrations::sqlite::run_migrations; +#[cfg(feature = "postgres")] +pub use postgres::{postgres_connection_pool, NostrPostgres}; diff --git a/database/nostr-sqldb/src/migrations/mod.rs b/database/nostr-sqldb/src/migrations/mod.rs new file mode 100644 index 000000000..13c12d36d --- /dev/null +++ b/database/nostr-sqldb/src/migrations/mod.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "mysql")] +pub mod mysql; + +#[cfg(feature = "postgres")] +pub mod postgres; + +#[cfg(feature = "sqlite")] +pub mod sqlite; diff --git a/database/nostr-sqldb/src/migrations/mysql.rs b/database/nostr-sqldb/src/migrations/mysql.rs new file mode 100644 index 000000000..ae3f171c8 --- /dev/null +++ b/database/nostr-sqldb/src/migrations/mysql.rs @@ -0,0 +1,19 @@ +use diesel::{Connection, MysqlConnection}; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use nostr_database::DatabaseError; +use tracing::info; + +const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/mysql"); + +/// programatically run the db migrations +pub fn run_migrations(connection_string: &str) -> Result<(), DatabaseError> { + info!("Running db migrations in mysql database",); + let mut connection = + MysqlConnection::establish(connection_string).map_err(DatabaseError::backend)?; + + let res = connection + .run_pending_migrations(MIGRATIONS) + .map_err(DatabaseError::Backend)?; + info!("Successfully executed mysql db migrations {:?}", res); + Ok(()) +} diff --git a/database/nostr-sqldb/src/migrations/postgres.rs b/database/nostr-sqldb/src/migrations/postgres.rs new file mode 100644 index 000000000..f54b32080 --- /dev/null +++ b/database/nostr-sqldb/src/migrations/postgres.rs @@ -0,0 +1,19 @@ +use diesel::{Connection, PgConnection}; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use nostr_database::DatabaseError; +use tracing::info; + +const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/postgres"); + +/// programatically run the db migrations +pub fn run_migrations(connection_string: &str) -> Result<(), DatabaseError> { + info!("Running db migrations in postgres database",); + let mut connection = + PgConnection::establish(connection_string).map_err(DatabaseError::backend)?; + + let res = connection + .run_pending_migrations(MIGRATIONS) + .map_err(DatabaseError::Backend)?; + info!("Successfully executed postgres db migrations {:?}", res); + Ok(()) +} diff --git a/database/nostr-sqldb/src/migrations/sqlite.rs b/database/nostr-sqldb/src/migrations/sqlite.rs new file mode 100644 index 000000000..34a3e943d --- /dev/null +++ b/database/nostr-sqldb/src/migrations/sqlite.rs @@ -0,0 +1,19 @@ +use diesel::{Connection, SqliteConnection}; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use nostr_database::DatabaseError; +use tracing::info; + +const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/sqlite"); + +/// programatically run the db migrations +pub fn run_migrations(connection_string: &str) -> Result<(), DatabaseError> { + info!("Running db migrations in sqlite database",); + let mut connection = + SqliteConnection::establish(connection_string).map_err(DatabaseError::backend)?; + + let res = connection + .run_pending_migrations(MIGRATIONS) + .map_err(DatabaseError::Backend)?; + info!("Successfully executed sqlite db migrations {:?}", res); + Ok(()) +} diff --git a/database/nostr-sqldb/src/model.rs b/database/nostr-sqldb/src/model.rs new file mode 100644 index 000000000..c1fec936b --- /dev/null +++ b/database/nostr-sqldb/src/model.rs @@ -0,0 +1,86 @@ +use std::sync::{Mutex, OnceLock}; + +use diesel::prelude::*; +use nostr::event::Event; +use nostr_database::{DatabaseError, FlatBufferBuilder, FlatBufferEncode}; + +#[cfg(feature = "mysql")] +use crate::schema::mysql::{event_tags, events}; +#[cfg(feature = "postgres")] +use crate::schema::postgres::{event_tags, events}; +#[cfg(feature = "sqlite")] +use crate::schema::sqlite::{event_tags, events}; + +/// DB representation of [`Event`] +#[derive(Queryable, Selectable, Insertable, AsChangeset, Debug, Clone)] +#[diesel(table_name = events)] +pub struct EventDb { + pub id: Vec, + pub pubkey: Vec, + pub created_at: i64, + pub kind: i64, + pub payload: Vec, + pub deleted: bool, +} + +/// DB representation of [`EventTag`] +#[derive(Queryable, Selectable, Insertable, AsChangeset, Debug, Clone)] +#[diesel(table_name = event_tags)] +pub struct EventTagDb { + pub tag: String, + pub tag_value: String, + pub event_id: Vec, +} + +/// A data container for extracting data from [`Event`] and its tags +#[derive(Debug, Clone)] +pub struct EventDataDb { + pub event: EventDb, + pub tags: Vec, +} + +impl TryFrom<&Event> for EventDataDb { + type Error = DatabaseError; + fn try_from(value: &Event) -> Result { + Ok(Self { + event: EventDb { + id: value.id.as_bytes().to_vec(), + pubkey: value.pubkey.as_bytes().to_vec(), + created_at: value.created_at.as_u64() as i64, + kind: value.kind.as_u16() as i64, + payload: encode_payload(value), + deleted: false, + }, + tags: extract_tags(value), + }) + } +} + +fn encode_payload(value: &Event) -> Vec { + static FB_BUILDER: OnceLock> = OnceLock::new(); + match FB_BUILDER + .get_or_init(|| Mutex::new(FlatBufferBuilder::new())) + .lock() + { + Ok(mut fb_builder) => value.encode(&mut fb_builder).to_vec(), + Err(_) => value.encode(&mut FlatBufferBuilder::new()).to_vec(), + } +} + +fn extract_tags(event: &Event) -> Vec { + event + .tags + .iter() + .filter_map(|tag| { + if let (kind, Some(content)) = (tag.kind(), tag.content()) { + Some(EventTagDb { + tag: kind.to_string(), + tag_value: content.to_string(), + event_id: event.id.as_bytes().to_vec(), + }) + } else { + None + } + }) + .collect() +} diff --git a/database/nostr-sqldb/src/postgres.rs b/database/nostr-sqldb/src/postgres.rs new file mode 100644 index 000000000..257ae49d3 --- /dev/null +++ b/database/nostr-sqldb/src/postgres.rs @@ -0,0 +1,235 @@ +use deadpool::managed::{Object, Pool}; +use diesel::prelude::*; +use diesel::result::{DatabaseErrorKind, Error as DieselError}; +use diesel::QueryResult; +use diesel_async::pooled_connection::AsyncDieselConnectionManager; +use diesel_async::scoped_futures::ScopedFutureExt; +use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; +use nostr::event::*; +use nostr::filter::Filter; +use nostr::types::Timestamp; +use nostr_database::*; +use prelude::BoxedFuture; + +use super::model::{EventDataDb, EventDb}; +use super::schema::postgres::{event_tags, events}; +use crate::query::{build_filter_query, event_by_id, with_limit}; + +/// Shorthand for a database connection pool type +pub type PostgresConnectionPool = Pool>; +pub type PostgresConnection = Object>; + +#[derive(Clone)] +pub struct NostrPostgres { + pool: PostgresConnectionPool, +} + +impl NostrPostgres { + /// Create a new [`NostrPostgres`] instance + pub async fn new(connection_string: C) -> Result + where + C: AsRef, + { + crate::migrations::postgres::run_migrations(connection_string.as_ref())?; + let pool = postgres_connection_pool(connection_string).await?; + Ok(Self { pool }) + } + + pub(crate) async fn get_connection(&self) -> Result { + self.pool.get().await.map_err(DatabaseError::backend) + } + + pub(crate) async fn save( + &self, + event_data: EventDataDb, + ) -> Result { + let mut db = self.get_connection().await?; + let result: QueryResult = db + .transaction(|c| { + async move { + diesel::insert_into(events::table) + .values(&event_data.event) + .execute(c) + .await?; + + diesel::insert_into(event_tags::table) + .values(&event_data.tags) + .execute(c) + .await?; + + Ok(true) + } + .scope_boxed() + }) + .await; + + match result { + Ok(_) => Ok(SaveEventStatus::Success), + Err(e) => match e { + DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _) => { + Ok(SaveEventStatus::Rejected(RejectedReason::Duplicate)) + } + e => Err(DatabaseError::backend(e)), + }, + } + } + + pub(crate) async fn event_by_id( + &self, + event_id: &EventId, + ) -> Result, DatabaseError> { + let res = event_by_id(event_id) + .first(&mut self.get_connection().await?) + .await + .optional() + .map_err(DatabaseError::backend)?; + Ok(res) + } +} + +impl NostrEventsDatabase for NostrPostgres { + /// Save [`Event`] into store + /// + /// **This method assumes that [`Event`] was already verified** + fn save_event<'a>( + &'a self, + event: &'a Event, + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { self.save(EventDataDb::try_from(event)?).await }) + } + + /// Check event status by ID + /// + /// Check if the event is saved, deleted or not existent. + fn check_id<'a>( + &'a self, + event_id: &'a EventId, + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { + let status = match self.event_by_id(event_id).await? { + Some(e) if e.deleted => DatabaseEventStatus::Deleted, + Some(_) => DatabaseEventStatus::Saved, + None => DatabaseEventStatus::NotExistent, + }; + Ok(status) + }) + } + + /// Coordinate feature is not supported yet + fn has_coordinate_been_deleted<'a>( + &'a self, + _coordinate: &'a nostr::nips::nip01::CoordinateBorrow<'a>, + _timestamp: &'a Timestamp, + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { Ok(false) }) + } + + /// Get [`Event`] by [`EventId`] + fn event_by_id<'a>( + &'a self, + event_id: &'a EventId, + ) -> BoxedFuture<'a, Result, DatabaseError>> { + Box::pin(async move { + let event = match self.event_by_id(event_id).await? { + Some(e) if !e.deleted => { + Some(Event::decode(&e.payload).map_err(DatabaseError::backend)?) + } + _ => None, + }; + Ok(event) + }) + } + + /// Count the number of events found with [`Filter`]. + /// + /// Use `Filter::new()` or `Filter::default()` to count all events. + fn count(&self, filter: Filter) -> BoxedFuture> { + Box::pin(async move { + let res: i64 = build_filter_query(filter) + .count() + .get_result(&mut self.get_connection().await?) + .await + .map_err(DatabaseError::backend)?; + Ok(res as usize) + }) + } + + /// Query stored events. + fn query(&self, filter: Filter) -> BoxedFuture> { + let filter = with_limit(filter, 10000); + Box::pin(async move { + let mut events = Events::new(&filter); + let result = build_filter_query(filter.clone()) + .select(EventDb::as_select()) + .load(&mut self.get_connection().await?) + .await + .map_err(DatabaseError::backend)?; + + for item in result.into_iter() { + if let Ok(event) = Event::decode(&item.payload) { + events.insert(event); + } + } + Ok(events) + }) + } + + /// Delete all events that match the [Filter] + fn delete(&self, filter: Filter) -> BoxedFuture> { + let filter = with_limit(filter, 999); + Box::pin(async move { + let filter = build_filter_query(filter); + diesel::update(events::table) + .set(events::deleted.eq(true)) + .filter(events::id.eq_any(filter.select(events::id))) + .execute(&mut self.get_connection().await?) + .await + .map_err(DatabaseError::backend)?; + + Ok(()) + }) + } +} + +impl NostrDatabase for NostrPostgres { + fn backend(&self) -> Backend { + Backend::Custom("Postgres".to_string()) + } +} + +/// Create a new [`NostrPostgres`] instance from an existing connection pool +impl From for NostrPostgres { + fn from(pool: PostgresConnectionPool) -> Self { + Self { pool } + } +} + +/// Create a connection pool for a Postgres database with the given connection string. +pub async fn postgres_connection_pool( + connection_string: C, +) -> Result +where + C: AsRef, +{ + let config = AsyncDieselConnectionManager::::new(connection_string.as_ref()); + let pool: PostgresConnectionPool = Pool::builder(config) + .build() + .map_err(|e| DatabaseError::Backend(Box::new(e)))?; + Ok(pool) +} + +impl std::fmt::Debug for NostrPostgres { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NostrPostgres") + .field("pool", &self.pool.status()) + .finish() + } +} + +/// For now we want to avoid wiping the database +impl NostrDatabaseWipe for NostrPostgres { + #[inline] + fn wipe(&self) -> BoxedFuture> { + Box::pin(async move { Err(DatabaseError::NotSupported) }) + } +} diff --git a/database/nostr-sqldb/src/query.rs b/database/nostr-sqldb/src/query.rs new file mode 100644 index 000000000..272536b3d --- /dev/null +++ b/database/nostr-sqldb/src/query.rs @@ -0,0 +1,126 @@ +use diesel::dsl::{AsSelect, Eq, Filter as DieselFilter, InnerJoin, IntoBoxed, SqlTypeOf}; +use diesel::expression::SqlLiteral; +use diesel::prelude::*; +use diesel::sql_types::Binary; +use nostr::event::*; +use nostr::filter::Filter; +use nostr_database::*; + +use super::model::EventDb; +#[cfg(feature = "mysql")] +use super::schema::mysql::{event_tags, events}; +#[cfg(feature = "postgres")] +use super::schema::postgres::{event_tags, events}; +#[cfg(feature = "sqlite")] +use super::schema::sqlite::{event_tags, events}; + +// filter type of a join query. +type QuerySetJoinTypeDb<'a, DB> = IntoBoxed< + 'a, + DieselFilter< + InnerJoin, + Eq>, + >, + DB, +>; +type SelectEventTypeDb = SqlTypeOf>; +type BoxedEventQueryDb<'a, DB> = events::BoxedQuery<'a, DB, SelectEventTypeDb>; + +#[cfg(feature = "postgres")] +type QuerySetJoinType<'a> = QuerySetJoinTypeDb<'a, diesel::pg::Pg>; +#[cfg(feature = "postgres")] +type BoxedEventQuery<'a> = BoxedEventQueryDb<'a, diesel::pg::Pg>; +#[cfg(feature = "sqlite")] +type QuerySetJoinType<'a> = QuerySetJoinTypeDb<'a, diesel::sqlite::Sqlite>; +#[cfg(feature = "sqlite")] +type BoxedEventQuery<'a> = BoxedEventQueryDb<'a, diesel::sqlite::Sqlite>; +#[cfg(feature = "mysql")] +type QuerySetJoinType<'a> = QuerySetJoinTypeDb<'a, diesel::mysql::Mysql>; +#[cfg(feature = "mysql")] +type BoxedEventQuery<'a> = BoxedEventQueryDb<'a, diesel::mysql::Mysql>; + +pub fn build_filter_query<'a>(filter: Filter) -> QuerySetJoinType<'a> { + let mut query = events::table + .distinct() + .inner_join(event_tags::table) + .filter(events::deleted.eq(false)) + .order_by(events::created_at.desc()) + .into_boxed(); + + if let Some(limit) = filter.limit { + query = query.limit(limit as i64); + } + + if !has_filters(&filter) { + return query; + } + + if let Some(ids) = filter.ids.clone() { + let values = ids + .iter() + .map(|id| id.as_bytes().to_vec()) + .collect::>(); + query = query.filter(events::id.eq_any(values)); + } + + if let Some(authors) = filter.authors.clone() { + let values = authors + .iter() + .map(|a| a.as_bytes().to_vec()) + .collect::>(); + query = query.filter(events::pubkey.eq_any(values)); + } + + if let Some(kinds) = filter.kinds.clone() { + let values = kinds.iter().map(|k| k.as_u16() as i64).collect::>(); + query = query.filter(events::kind.eq_any(values)); + } + + if let Some(since) = filter.since { + query = query.filter(events::created_at.ge(since.as_u64() as i64)); + } + + if let Some(until) = filter.until { + query = query.filter(events::created_at.le(until.as_u64() as i64)); + } + + if !filter.generic_tags.is_empty() { + for (tag, values) in filter.generic_tags.into_iter() { + let values = values.iter().map(|v| v.to_string()).collect::>(); + query = query.filter( + event_tags::tag + .eq(tag.to_string()) + .and(event_tags::tag_value.eq_any(values)), + ); + } + } + + query +} + +/// sets the given default limit on a Nostr filter if not set +pub fn with_limit(filter: Filter, default_limit: usize) -> Filter { + if filter.limit.is_none() { + return filter.limit(default_limit); + } + filter +} + +pub fn event_by_id<'a>(event_id: &EventId) -> BoxedEventQuery<'a> { + let event_id = event_id.as_bytes().to_vec(); + events::table + .select(EventDb::as_select()) + .filter(events::id.eq(event_id)) + .into_boxed() +} + +// determine if the filter has any filters set +fn has_filters(filter: &Filter) -> bool { + filter.ids.is_some() + || filter.authors.is_some() + || filter.kinds.is_some() + || filter.since.is_some() + || filter.until.is_some() + || !filter.generic_tags.is_empty() + || filter.limit.is_some() +} diff --git a/database/nostr-sqldb/src/schema/mod.rs b/database/nostr-sqldb/src/schema/mod.rs new file mode 100644 index 000000000..13f8538b0 --- /dev/null +++ b/database/nostr-sqldb/src/schema/mod.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "postgres")] +pub mod postgres; + +#[cfg(feature = "mysql")] +pub mod mysql; + +#[cfg(feature = "sqlite")] +pub mod sqlite; diff --git a/database/nostr-sqldb/src/schema/mysql.rs b/database/nostr-sqldb/src/schema/mysql.rs new file mode 100644 index 000000000..e37f29370 --- /dev/null +++ b/database/nostr-sqldb/src/schema/mysql.rs @@ -0,0 +1,29 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + event_tags (tag, tag_value, event_id) { + #[max_length = 64] + tag -> Varchar, + #[max_length = 512] + tag_value -> Varchar, + #[max_length = 32] + event_id -> Blob, + } +} + +diesel::table! { + events (id) { + #[max_length = 32] + id -> Blob, + #[max_length = 32] + pubkey -> Blob, + created_at -> Bigint, + kind -> Bigint, + payload -> Blob, + deleted -> Bool, + } +} + +diesel::joinable!(event_tags -> events (event_id)); + +diesel::allow_tables_to_appear_in_same_query!(event_tags, events); diff --git a/database/nostr-sqldb/src/schema/postgres.rs b/database/nostr-sqldb/src/schema/postgres.rs new file mode 100644 index 000000000..629ffcf19 --- /dev/null +++ b/database/nostr-sqldb/src/schema/postgres.rs @@ -0,0 +1,24 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + event_tags (tag, tag_value, event_id) { + tag -> Text, + tag_value -> Text, + event_id -> Bytea, + } +} + +diesel::table! { + events (id) { + id -> Bytea, + pubkey -> Bytea, + created_at -> Int8, + kind -> Int8, + payload -> Bytea, + deleted -> Bool, + } +} + +diesel::joinable!(event_tags -> events (event_id)); + +diesel::allow_tables_to_appear_in_same_query!(event_tags, events); diff --git a/database/nostr-sqldb/src/schema/sqlite.rs b/database/nostr-sqldb/src/schema/sqlite.rs new file mode 100644 index 000000000..0a7973a3a --- /dev/null +++ b/database/nostr-sqldb/src/schema/sqlite.rs @@ -0,0 +1,24 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + event_tags (tag, tag_value, event_id) { + tag -> Text, + tag_value -> Text, + event_id -> Binary, + } +} + +diesel::table! { + events (id) { + id -> Binary, + pubkey -> Binary, + created_at -> BigInt, + kind -> BigInt, + payload -> Binary, + deleted -> Bool, + } +} + +diesel::joinable!(event_tags -> events (event_id)); + +diesel::allow_tables_to_appear_in_same_query!(event_tags, events); From d2b735cccef0d5dee396dd384adad1464aa79bce Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 30 Apr 2025 10:10:45 +0200 Subject: [PATCH 2/8] contrib: skip MSRV for nostr-sqldb Signed-off-by: Yuki Kishimoto --- contrib/scripts/check-crates.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/scripts/check-crates.sh b/contrib/scripts/check-crates.sh index 6344ca4d8..a06e3bae2 100755 --- a/contrib/scripts/check-crates.sh +++ b/contrib/scripts/check-crates.sh @@ -58,6 +58,9 @@ buildargs=( skip_msrv=( "-p nostr-lmdb" # MSRV: 1.72.0 + "-p nostr-sqldb --no-default-features --features postgres" # MSRV: 1.82.0 + "-p nostr-sqldb --no-default-features --features mysql" # MSRV: 1.82.0 + "-p nostr-sqldb --no-default-features --features sqlite" # MSRV: 1.82.0 "-p nostr-keyring" # MSRV: 1.75.0 "-p nostr-keyring --features async" # MSRV: 1.75.0 "-p nostr-sdk --features tor" # MSRV: 1.77.0 From 50af7a4f0994fade32b5f00f15643dd10a4782d2 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 30 Apr 2025 10:19:26 +0200 Subject: [PATCH 3/8] sqldb: add crate-level attributes Signed-off-by: Yuki Kishimoto --- database/nostr-sqldb/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/database/nostr-sqldb/src/lib.rs b/database/nostr-sqldb/src/lib.rs index a1c3456fd..7c464c62e 100644 --- a/database/nostr-sqldb/src/lib.rs +++ b/database/nostr-sqldb/src/lib.rs @@ -1,3 +1,9 @@ +#![forbid(unsafe_code)] +#![warn(missing_docs)] +#![warn(rustdoc::bare_urls)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] + mod migrations; #[allow(dead_code)] // TODO: to remove when also SQLite and MySQL are implemented mod model; From d43ba47c19b2b18335b825ba0c69ebb0d5dbb2cb Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 30 Apr 2025 10:24:53 +0200 Subject: [PATCH 4/8] Add `nostr-sqldb` to README.md Signed-off-by: Yuki Kishimoto --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 784399459..86fd856f4 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ The project is split up into several crates: - [**nostr-database**](./database/nostr-database): Events database abstraction and in-memory implementation - [**nostr-lmdb**](./database/nostr-lmdb): LMDB storage backend - [**nostr-ndb**](./database/nostr-ndb): [nostrdb](https://github.com/damus-io/nostrdb) storage backend + - [**nostr-sqldb**](./database/nostr-sqldb): SQL storage backends (PostgreSQL, MySQL and SQLite) - [**nostr-indexeddb**](./database/nostr-indexeddb): IndexedDB storage backend - [**nostr-gossip**](./gossip/nostr-gossip): Gossip traits - [**nostr-gossip-memory**](./gossip/nostr-gossip-memory): In-memory gossip database From dd4cd8bff5bb695cf27f276e524e1820162de7f9 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Tue, 8 Jul 2025 11:21:19 +0200 Subject: [PATCH 5/8] sqldb: move from diesel to sqlx Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1612 +++++------------ crates/nostr-sdk/examples/client.rs | 21 +- database/nostr-sqldb/Cargo.toml | 36 +- database/nostr-sqldb/build.rs | 7 + .../nostr-sqldb/examples/postgres-relay.rs | 19 +- database/nostr-sqldb/examples/sqlite-relay.rs | 32 + database/nostr-sqldb/migrations/.keep | 0 database/nostr-sqldb/migrations/mysql/.keep | 0 .../up.sql => 001_init.sql} | 4 +- .../mysql/2025-04-11-095120_events/down.sql | 3 - .../down.sql | 6 - .../up.sql | 36 - .../up.sql => 001_init.sql} | 0 .../2025-04-11-095120_events/down.sql | 3 - database/nostr-sqldb/migrations/sqlite/.keep | 0 .../up.sql => 001_init.sql} | 0 .../sqlite/2025-04-11-095120_events/down.sql | 3 - database/nostr-sqldb/mysql.toml | 9 - database/nostr-sqldb/postgres.toml | 9 - database/nostr-sqldb/sqlite.toml | 9 - database/nostr-sqldb/src/db.rs | 756 ++++++++ database/nostr-sqldb/src/error.rs | 41 + database/nostr-sqldb/src/lib.rs | 27 +- database/nostr-sqldb/src/migrations/mod.rs | 8 - database/nostr-sqldb/src/migrations/mysql.rs | 19 - .../nostr-sqldb/src/migrations/postgres.rs | 19 - database/nostr-sqldb/src/migrations/sqlite.rs | 19 - database/nostr-sqldb/src/model.rs | 61 +- database/nostr-sqldb/src/postgres.rs | 235 --- database/nostr-sqldb/src/query.rs | 126 -- database/nostr-sqldb/src/schema/mod.rs | 8 - database/nostr-sqldb/src/schema/mysql.rs | 29 - database/nostr-sqldb/src/schema/postgres.rs | 24 - database/nostr-sqldb/src/schema/sqlite.rs | 24 - 34 files changed, 1392 insertions(+), 1813 deletions(-) create mode 100644 database/nostr-sqldb/build.rs create mode 100644 database/nostr-sqldb/examples/sqlite-relay.rs delete mode 100644 database/nostr-sqldb/migrations/.keep delete mode 100644 database/nostr-sqldb/migrations/mysql/.keep rename database/nostr-sqldb/migrations/mysql/{2025-04-11-095120_events/up.sql => 001_init.sql} (90%) delete mode 100644 database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/down.sql delete mode 100644 database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/down.sql delete mode 100644 database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/up.sql rename database/nostr-sqldb/migrations/postgres/{2025-04-11-095120_events/up.sql => 001_init.sql} (100%) delete mode 100644 database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/down.sql delete mode 100644 database/nostr-sqldb/migrations/sqlite/.keep rename database/nostr-sqldb/migrations/sqlite/{2025-04-11-095120_events/up.sql => 001_init.sql} (100%) delete mode 100644 database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/down.sql delete mode 100644 database/nostr-sqldb/mysql.toml delete mode 100644 database/nostr-sqldb/postgres.toml delete mode 100644 database/nostr-sqldb/sqlite.toml create mode 100644 database/nostr-sqldb/src/db.rs create mode 100644 database/nostr-sqldb/src/error.rs delete mode 100644 database/nostr-sqldb/src/migrations/mod.rs delete mode 100644 database/nostr-sqldb/src/migrations/mysql.rs delete mode 100644 database/nostr-sqldb/src/migrations/postgres.rs delete mode 100644 database/nostr-sqldb/src/migrations/sqlite.rs delete mode 100644 database/nostr-sqldb/src/postgres.rs delete mode 100644 database/nostr-sqldb/src/query.rs delete mode 100644 database/nostr-sqldb/src/schema/mod.rs delete mode 100644 database/nostr-sqldb/src/schema/mysql.rs delete mode 100644 database/nostr-sqldb/src/schema/postgres.rs delete mode 100644 database/nostr-sqldb/src/schema/sqlite.rs diff --git a/Cargo.lock b/Cargo.lock index b42d4dc5d..0fc33bb3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,20 +51,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "ahash" version = "0.8.11" @@ -74,7 +60,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -226,7 +212,7 @@ dependencies = [ "libc", "once_cell", "postage", - "rand 0.8.5", + "rand", "safelog", "serde", "thiserror 2.0.8", @@ -386,6 +372,15 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic" version = "0.5.3" @@ -597,15 +592,6 @@ dependencies = [ "serde", ] -[[package]] -name = "btoi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" -dependencies = [ - "num-traits", -] - [[package]] name = "bumpalo" version = "3.14.0" @@ -632,9 +618,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "caret" @@ -657,8 +643,6 @@ version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ - "jobserver", - "libc", "shlex", ] @@ -683,12 +667,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chacha20" version = "0.9.1" @@ -788,24 +766,6 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" -[[package]] -name = "clipboard-win" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" -dependencies = [ - "error-code", -] - -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] - [[package]] name = "coarsetime" version = "0.1.34" @@ -848,10 +808,10 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ - "encode_unicode 0.3.6", + "encode_unicode", "lazy_static", "libc", - "unicode-width 0.1.14", + "unicode-width", "windows-sys 0.52.0", ] @@ -924,53 +884,27 @@ dependencies = [ ] [[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "crc" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ - "crossbeam-utils", + "crc-catalog", ] [[package]] -name = "crossbeam-deque" -version = "0.8.6" +name = "crc-catalog" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "crc32fast" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "crossbeam-utils", + "cfg-if", ] [[package]] @@ -995,7 +929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -1007,31 +941,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "typenum", ] -[[package]] -name = "csv" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" -dependencies = [ - "memchr", -] - [[package]] name = "ctr" version = "0.9.2" @@ -1242,7 +1155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b84d32b18d9a256d81e4fec2e4cfd0ab6dde5e5ff49be1713ae0adbd0060c2" dependencies = [ "heck 0.5.0", - "indexmap 2.5.0", + "indexmap 1.9.3", "itertools 0.12.1", "proc-macro-crate", "proc-macro2", @@ -1260,7 +1173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b48e8e38a4aa565da767322b5ca55fb0f8347983c5bc7f7647db069405420479" dependencies = [ "heck 0.5.0", - "indexmap 2.5.0", + "indexmap 1.9.3", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", @@ -1359,76 +1272,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "diesel" -version = "2.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d3950690ba3a6910126162b47e775e203006d4242a15de912bec6c0a695153" -dependencies = [ - "bitflags 2.9.0", - "byteorder", - "diesel_derives", - "itoa", - "libsqlite3-sys", - "mysqlclient-sys", - "percent-encoding", - "pq-sys", - "serde_json", - "time", - "url", -] - -[[package]] -name = "diesel-async" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a307ac00f7c23f526a04a77761a0519b9f0eb2838ebf5b905a58580095bdcb" -dependencies = [ - "async-trait", - "deadpool", - "diesel", - "futures-channel", - "futures-util", - "mysql_async", - "mysql_common", - "scoped-futures", - "tokio", - "tokio-postgres", -] - -[[package]] -name = "diesel_derives" -version = "2.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93958254b70bea63b4187ff73d10180599d9d8d177071b7f91e6da4e0c0ad55" -dependencies = [ - "diesel_table_macro_syntax", - "dsl_auto_type", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "diesel_migrations" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" -dependencies = [ - "diesel", - "migrations_internals", - "migrations_macros", -] - -[[package]] -name = "diesel_table_macro_syntax" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" -dependencies = [ - "syn 2.0.90", -] - [[package]] name = "digest" version = "0.10.7" @@ -1468,16 +1311,6 @@ dependencies = [ "dirs-sys 0.5.0", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.4.1" @@ -1502,17 +1335,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users 0.4.6", - "winapi", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -1524,6 +1346,12 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "2.0.1" @@ -1539,20 +1367,6 @@ dependencies = [ "phf", ] -[[package]] -name = "dsl_auto_type" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" -dependencies = [ - "darling 0.20.10", - "either", - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "dyn-clone" version = "1.0.17" @@ -1592,7 +1406,7 @@ dependencies = [ "curve25519-dalek", "ed25519", "merlin", - "rand_core 0.6.4", + "rand_core", "serde", "sha2", "subtle", @@ -1616,6 +1430,9 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -1629,10 +1446,8 @@ dependencies = [ "ff", "generic-array", "group", - "hkdf", - "pem-rfc7468", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -1644,12 +1459,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - [[package]] name = "enum-ordinalize" version = "3.1.15" @@ -1693,10 +1502,15 @@ dependencies = [ ] [[package]] -name = "error-code" -version = "3.3.1" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] [[package]] name = "event-listener" @@ -1709,12 +1523,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1745,24 +1553,13 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" -[[package]] -name = "fd-lock" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" -dependencies = [ - "cfg-if", - "rustix 0.38.44", - "windows-sys 0.52.0", -] - [[package]] name = "ff" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -1833,6 +1630,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "749cff877dc1af878a0b31a41dd221a753634401ea0ef2f87b62d3171522485a" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1949,6 +1758,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -2043,16 +1863,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - [[package]] name = "gimli" version = "0.31.0" @@ -2090,7 +1900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2132,6 +1942,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "hashlink" version = "0.9.1" @@ -2141,6 +1957,15 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.2", +] + [[package]] name = "heck" version = "0.4.1" @@ -2247,46 +2072,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" -[[package]] -name = "hpke-rs" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11bd4ee27b79fa1820e72ef8489cc729c87299ec3f7f52b8fc8dcb87cb2d485" -dependencies = [ - "hpke-rs-crypto", - "log", - "serde", - "tls_codec", - "zeroize", -] - -[[package]] -name = "hpke-rs-crypto" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c3f1ae0a26c18d6469a70db1217136056261c4a244b09a755bc60bd4e055b67" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "hpke-rs-rust-crypto" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08d4500baf0aced746723d3515d08212bdb9d941df6d1aca3d46d1619b2a1cf" -dependencies = [ - "aes-gcm", - "chacha20poly1305", - "hkdf", - "hpke-rs-crypto", - "p256", - "p384", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "sha2", - "x25519-dalek", -] - [[package]] name = "http" version = "1.1.0" @@ -2477,26 +2262,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.16.0", "serde", -] - -[[package]] -name = "indicatif" -version = "0.17.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" -dependencies = [ - "console", - "number_prefix", - "portable-atomic", - "unicode-width 0.2.0", - "web-time", + "serde_core", ] [[package]] @@ -2616,15 +2389,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.77" @@ -2654,15 +2418,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "keyed_priority_queue" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" -dependencies = [ - "indexmap 2.5.0", -] - [[package]] name = "keyring" version = "3.6.2" @@ -2817,18 +2572,9 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.2", -] - -[[package]] -name = "lru" -version = "0.14.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198" +checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" [[package]] name = "macroific" @@ -2919,31 +2665,10 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core 0.6.4", + "rand_core", "zeroize", ] -[[package]] -name = "migrations_internals" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" -dependencies = [ - "serde", - "toml", -] - -[[package]] -name = "migrations_macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" -dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", -] - [[package]] name = "mime" version = "0.3.17" @@ -2989,75 +2714,12 @@ dependencies = [ ] [[package]] -name = "mysql_async" -version = "0.34.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b66e411c31265e879d9814d03721f2daa7ad07337b6308cb4bb0cde7e6fd47" -dependencies = [ - "bytes", - "crossbeam", - "flate2", - "futures-core", - "futures-sink", - "futures-util", - "keyed_priority_queue", - "lru 0.12.5", - "mysql_common", - "pem", - "percent-encoding", - "pin-project", - "rand 0.8.5", - "serde", - "serde_json", - "socket2", - "thiserror 1.0.64", - "tokio", - "tokio-util", - "twox-hash", - "url", -] - -[[package]] -name = "mysql_common" -version = "0.32.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478b0ff3f7d67b79da2b96f56f334431aef65e15ba4b29dd74a4236e29582bdc" -dependencies = [ - "base64 0.21.7", - "bindgen", - "bitflags 2.9.0", - "btoi", - "byteorder", - "bytes", - "cc", - "cmake", - "crc32fast", - "flate2", - "lazy_static", - "num-bigint", - "num-traits", - "rand 0.8.5", - "regex", - "saturating", - "serde", - "serde_json", - "sha1", - "sha2", - "smallvec", - "subprocess", - "thiserror 1.0.64", - "uuid", - "zstd", -] - -[[package]] -name = "mysqlclient-sys" -version = "0.4.4" +name = "nanorand" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29e21174d84e2622ceb7b0146a9187d36458a3a9ee9a66c9cac22e96493ef9" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "pkg-config", - "vcpkg", + "getrandom 0.2.15", ] [[package]] @@ -3072,29 +2734,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" -[[package]] -name = "nip07" -version = "0.42.0" -dependencies = [ - "js-sys", - "nostr", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.9.0", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -3107,7 +2746,7 @@ dependencies = [ [[package]] name = "nostr" -version = "0.42.1" +version = "0.44.1" dependencies = [ "aes", "base64 0.22.1", @@ -3118,6 +2757,7 @@ dependencies = [ "chacha20", "chacha20poly1305", "getrandom 0.2.15", + "hex", "instant", "nostr-ots", "reqwest", @@ -3133,7 +2773,7 @@ dependencies = [ [[package]] name = "nostr-blossom" -version = "0.42.0" +version = "0.44.0" dependencies = [ "base64 0.22.1", "clap", @@ -3144,26 +2784,37 @@ dependencies = [ ] [[package]] -name = "nostr-cli" -version = "0.42.0" +name = "nostr-browser-signer" +version = "0.44.1" dependencies = [ - "clap", - "dialoguer", - "dirs 5.0.1", - "indicatif", - "nostr-connect", - "nostr-relay-builder", - "nostr-sdk", - "once_cell", - "prettytable-rs", - "regex", - "rustyline", + "js-sys", + "nostr", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "nostr-browser-signer-proxy" +version = "0.44.0" +dependencies = [ + "atomic-destructor", + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "nostr", + "serde", + "serde_json", "tokio", + "tracing", + "tracing-subscriber", + "uuid", ] [[package]] name = "nostr-connect" -version = "0.42.0" +version = "0.44.0" dependencies = [ "async-utility", "dialoguer", @@ -3177,7 +2828,7 @@ dependencies = [ [[package]] name = "nostr-database" -version = "0.42.0" +version = "0.44.0" dependencies = [ "flatbuffers 25.2.10", "lru", @@ -3222,7 +2873,7 @@ dependencies = [ [[package]] name = "nostr-http-file-storage" -version = "0.42.0" +version = "0.44.0" dependencies = [ "nostr", "reqwest", @@ -3231,8 +2882,9 @@ dependencies = [ [[package]] name = "nostr-indexeddb" -version = "0.42.0" +version = "0.44.0" dependencies = [ + "hex", "indexed_db_futures", "nostr", "nostr-database", @@ -3240,7 +2892,7 @@ dependencies = [ [[package]] name = "nostr-keyring" -version = "0.42.0" +version = "0.44.1" dependencies = [ "async-utility", "keyring", @@ -3250,9 +2902,11 @@ dependencies = [ [[package]] name = "nostr-lmdb" -version = "0.42.0" +version = "0.44.0" dependencies = [ "async-utility", + "flume", + "futures", "heed", "nostr", "nostr-database", @@ -3262,71 +2916,11 @@ dependencies = [ "tracing", ] -[[package]] -name = "nostr-mls" -version = "0.42.0" -dependencies = [ - "aes-gcm", - "nostr", - "nostr-mls-memory-storage", - "nostr-mls-sqlite-storage", - "nostr-mls-storage", - "openmls", - "openmls_basic_credential", - "openmls_rust_crypto", - "openmls_traits", - "tempfile", - "tls_codec", - "tokio", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "nostr-mls-memory-storage" -version = "0.42.0" -dependencies = [ - "aes-gcm", - "lru", - "nostr", - "nostr-mls-storage", - "openmls", - "openmls_memory_storage", - "parking_lot", -] - -[[package]] -name = "nostr-mls-sqlite-storage" -version = "0.42.0" -dependencies = [ - "aes-gcm", - "nostr", - "nostr-mls-storage", - "openmls", - "openmls_sqlite_storage", - "refinery", - "rusqlite", - "serde", - "serde_json", - "tempfile", - "tracing", -] - -[[package]] -name = "nostr-mls-storage" -version = "0.42.0" -dependencies = [ - "nostr", - "openmls", - "openmls_traits", - "serde", - "serde_json", -] - [[package]] name = "nostr-ndb" -version = "0.42.0" +version = "0.44.0" dependencies = [ + "hex", "nostr", "nostr-database", "nostrdb", @@ -3347,12 +2941,13 @@ dependencies = [ [[package]] name = "nostr-relay-builder" -version = "0.42.0" +version = "0.44.0" dependencies = [ "async-utility", "async-wsocket", "atomic-destructor", "base64 0.22.1", + "hex", "hyper", "hyper-util", "negentropy", @@ -3365,12 +2960,13 @@ dependencies = [ [[package]] name = "nostr-relay-pool" -version = "0.42.0" +version = "0.44.0" dependencies = [ "async-utility", "async-wsocket", "atomic-destructor", - "lru 0.14.0", + "hex", + "lru", "negentropy", "nostr", "nostr-database", @@ -3382,13 +2978,14 @@ dependencies = [ [[package]] name = "nostr-sdk" -version = "0.42.0" +version = "0.44.1" dependencies = [ "async-utility", "nostr", "nostr-connect", "nostr-database", - "nostr-indexeddb", + "nostr-gossip", + "nostr-gossip-memory", "nostr-lmdb", "nostr-ndb", "nostr-relay-pool", @@ -3399,25 +2996,22 @@ dependencies = [ [[package]] name = "nostr-sqldb" -version = "0.41.0" +version = "0.42.0" dependencies = [ - "deadpool", - "diesel", - "diesel-async", - "diesel_migrations", "nostr", "nostr-database", "nostr-relay-builder", + "sqlx", + "tempfile", "tokio", - "tracing", "tracing-subscriber", ] [[package]] name = "nostrdb" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b01d00781031ad2d6837a178e0e6ec343ff4ffc36a4510a583ed6cc67e159" +checksum = "195bddbd8f436caa9fe3edbcba5743a4386a7ab03b1b81b59abf5b42262edfe5" dependencies = [ "bindgen", "cc", @@ -3499,7 +3093,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand", "smallvec", "zeroize", ] @@ -3560,16 +3154,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "num_enum" version = "0.7.3" @@ -3591,17 +3175,10 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "nwc" -version = "0.42.0" +version = "0.44.0" dependencies = [ - "async-utility", "nostr", "nostr-relay-pool", "tokio", @@ -3665,122 +3242,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] -name = "openmls" -version = "0.6.1" -source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +name = "opentimestamps" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426fc5f60c647b5d49210a1fb1facea0962a91e60b320b217b15dc06d99f12ba" dependencies = [ + "bitcoin_hashes 0.12.0", + "env_logger", "log", - "openmls_traits", - "rayon", - "serde", - "thiserror 2.0.8", - "tls_codec", ] [[package]] -name = "openmls_basic_credential" -version = "0.3.0" -source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ - "ed25519-dalek", - "openmls_traits", - "p256", - "rand 0.8.5", - "serde", - "tls_codec", + "num-traits", ] [[package]] -name = "openmls_memory_storage" -version = "0.3.0" -source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" dependencies = [ - "log", - "openmls_traits", - "serde", - "serde_json", - "thiserror 2.0.8", -] - -[[package]] -name = "openmls_rust_crypto" -version = "0.3.0" -source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" -dependencies = [ - "aes-gcm", - "chacha20poly1305", - "ed25519-dalek", - "hkdf", - "hmac", - "hpke-rs", - "hpke-rs-crypto", - "hpke-rs-rust-crypto", - "openmls_memory_storage", - "openmls_traits", - "p256", - "rand 0.8.5", - "rand_chacha 0.3.1", - "serde", - "sha2", - "thiserror 2.0.8", - "tls_codec", -] - -[[package]] -name = "openmls_sqlite_storage" -version = "0.1.0" -source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" -dependencies = [ - "log", - "openmls_traits", - "refinery", - "rusqlite", - "serde", - "thiserror 1.0.64", -] - -[[package]] -name = "openmls_traits" -version = "0.3.0" -source = "git+https://github.com/openmls/openmls?rev=4cc0f594b11262083ad9827b3b2033052c6ef99f#4cc0f594b11262083ad9827b3b2033052c6ef99f" -dependencies = [ - "serde", - "tls_codec", -] - -[[package]] -name = "opentimestamps" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426fc5f60c647b5d49210a1fb1facea0962a91e60b320b217b15dc06d99f12ba" -dependencies = [ - "bitcoin_hashes 0.12.0", - "env_logger", - "log", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" -dependencies = [ - "memchr", + "memchr", ] [[package]] @@ -3823,7 +3316,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "rand_core 0.6.4", + "rand_core", "sha2", ] @@ -3873,7 +3366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -3893,16 +3386,6 @@ dependencies = [ "hmac", ] -[[package]] -name = "pem" -version = "3.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" -dependencies = [ - "base64 0.22.1", - "serde", -] - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3935,7 +3418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand", ] [[package]] @@ -3957,7 +3440,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -4030,24 +3513,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "postage" version = "0.5.0" @@ -4063,35 +3528,6 @@ dependencies = [ "thiserror 1.0.64", ] -[[package]] -name = "postgres-protocol" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" -dependencies = [ - "base64 0.22.1", - "byteorder", - "bytes", - "fallible-iterator 0.2.0", - "hmac", - "md-5", - "memchr", - "rand 0.9.0", - "sha2", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" -dependencies = [ - "bytes", - "fallible-iterator 0.2.0", - "postgres-protocol", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -4104,17 +3540,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy 0.7.35", -] - -[[package]] -name = "pq-sys" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c852911b98f5981956037b2ca976660612e548986c30af075e753107bc3400" -dependencies = [ - "libc", - "vcpkg", + "zerocopy", ] [[package]] @@ -4127,20 +3553,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "prettytable-rs" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" -dependencies = [ - "csv", - "encode_unicode 1.0.0", - "is-terminal", - "lazy_static", - "term", - "unicode-width 0.1.14", -] - [[package]] name = "primeorder" version = "0.13.6" @@ -4158,7 +3570,7 @@ checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", - "indexmap 2.5.0", + "indexmap 2.12.0", ] [[package]] @@ -4216,7 +3628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", - "rand 0.8.5", + "rand", "ring 0.17.14", "rustc-hash 2.0.0", "rustls", @@ -4261,19 +3673,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", - "zerocopy 0.8.24", + "rand_chacha", + "rand_core", ] [[package]] @@ -4283,17 +3684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -4305,41 +3696,12 @@ dependencies = [ "getrandom 0.2.15", ] -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.1", -] - [[package]] name = "rangemap" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.5.5" @@ -4371,50 +3733,6 @@ dependencies = [ "thiserror 2.0.8", ] -[[package]] -name = "refinery" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba5d693abf62492c37268512ff35b77655d2e957ca53dab85bf993fe9172d15" -dependencies = [ - "refinery-core", - "refinery-macros", -] - -[[package]] -name = "refinery-core" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a83581f18c1a4c3a6ebd7a174bdc665f17f618d79f7edccb6a0ac67e660b319" -dependencies = [ - "async-trait", - "cfg-if", - "log", - "regex", - "rusqlite", - "serde", - "siphasher 1.0.1", - "thiserror 1.0.64", - "time", - "toml", - "url", - "walkdir", -] - -[[package]] -name = "refinery-macros" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c225407d8e52ef8cf094393781ecda9a99d6544ec28d90a6915751de259264" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "refinery-core", - "regex", - "syn 2.0.90", -] - [[package]] name = "regex" version = "1.11.1" @@ -4561,7 +3879,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sha2", "signature", "spki", @@ -4576,9 +3894,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ "bitflags 2.9.0", - "fallible-iterator 0.3.0", + "fallible-iterator", "fallible-streaming-iterator", - "hashlink", + "hashlink 0.9.1", "libsqlite3-sys", "smallvec", "time", @@ -4694,26 +4012,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" -[[package]] -name = "rustyline" -version = "16.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62fd9ca5ebc709e8535e8ef7c658eb51457987e48c98ead2be482172accc408d" -dependencies = [ - "bitflags 2.9.0", - "cfg-if", - "clipboard-win", - "fd-lock", - "libc", - "log", - "memchr", - "nix", - "unicode-segmentation", - "unicode-width 0.2.0", - "utf8parse", - "windows-sys 0.59.0", -] - [[package]] name = "ryu" version = "1.0.18" @@ -4760,21 +4058,6 @@ dependencies = [ "regex", ] -[[package]] -name = "saturating" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" - -[[package]] -name = "scoped-futures" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b24aae2d0636530f359e9d5ef0c04669d11c5e756699b27a6a6d845d8329091" -dependencies = [ - "pin-project-lite", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -4813,7 +4096,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "rand 0.8.5", + "rand", "secp256k1-sys", "serde", ] @@ -4871,10 +4154,11 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -4897,11 +4181,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -4960,7 +4253,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.5.0", + "indexmap 2.12.0", "serde", "serde_derive", "serde_json", @@ -5060,7 +4353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -5069,12 +4362,6 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "slab" version = "0.4.9" @@ -5085,64 +4372,260 @@ dependencies = [ ] [[package]] -name = "slotmap" -version = "1.0.7" +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "slotmap-careful" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a59a806f2fa2def01b97bba3a7947cf47daac2ddf6a1b0701063aeafe5b129" +dependencies = [ + "paste", + "serde", + "slotmap", + "thiserror 2.0.8", + "void", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.2", + "hashlink 0.10.0", + "indexmap 2.12.0", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rustls", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.8", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.90", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", "serde", - "version_check", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.90", + "tokio", + "url", ] [[package]] -name = "slotmap-careful" -version = "0.2.2" +name = "sqlx-mysql" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0a59a806f2fa2def01b97bba3a7947cf47daac2ddf6a1b0701063aeafe5b129" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ - "paste", + "atoi", + "base64 0.22.1", + "bitflags 2.9.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", "serde", - "slotmap", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", "thiserror 2.0.8", - "void", + "tracing", + "whoami", ] [[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.9" +name = "sqlx-postgres" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ - "libc", - "windows-sys 0.52.0", + "atoi", + "base64 0.22.1", + "bitflags 2.9.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.8", + "tracing", + "whoami", ] [[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" +name = "sqlx-sqlite" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ - "base64ct", - "der", + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.8", + "tracing", + "url", ] [[package]] @@ -5175,7 +4658,7 @@ dependencies = [ "p256", "p384", "p521", - "rand_core 0.6.4", + "rand_core", "rsa", "sec1", "sha2", @@ -5237,16 +4720,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "subprocess" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "subtle" version = "2.6.1" @@ -5312,9 +4785,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.1", @@ -5323,17 +4796,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -5448,28 +4910,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tls_codec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" -dependencies = [ - "serde", - "tls_codec_derive", - "zeroize", -] - -[[package]] -name = "tls_codec_derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "tokio" version = "1.45.1" @@ -5499,32 +4939,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "tokio-postgres" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator 0.2.0", - "futures-channel", - "futures-util", - "log", - "parking_lot", - "percent-encoding", - "phf", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand 0.9.0", - "socket2", - "tokio", - "tokio-util", - "whoami", -] - [[package]] name = "tokio-rustls" version = "0.26.0" @@ -5548,6 +4962,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.26.1" @@ -5605,7 +5030,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.12.0", "serde", "serde_spanned", "toml_datetime", @@ -5639,8 +5064,8 @@ dependencies = [ "itertools 0.14.0", "libc", "paste", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "serde", "slab", "smallvec", @@ -5679,7 +5104,7 @@ dependencies = [ "derive_more 2.0.1", "educe", "paste", - "rand 0.8.5", + "rand", "smallvec", "thiserror 2.0.8", "tor-basic-utils", @@ -5724,7 +5149,7 @@ dependencies = [ "futures", "oneshot-fused-workaround", "postage", - "rand 0.8.5", + "rand", "safelog", "serde", "thiserror 2.0.8", @@ -5778,7 +5203,7 @@ dependencies = [ "once_cell", "oneshot-fused-workaround", "pin-project", - "rand 0.8.5", + "rand", "retry-error", "safelog", "serde", @@ -5919,7 +5344,7 @@ dependencies = [ "oneshot-fused-workaround", "paste", "postage", - "rand 0.8.5", + "rand", "rusqlite", "safelog", "scopeguard", @@ -5996,7 +5421,7 @@ dependencies = [ "oneshot-fused-workaround", "pin-project", "postage", - "rand 0.8.5", + "rand", "safelog", "serde", "strum", @@ -6032,7 +5457,7 @@ dependencies = [ "itertools 0.14.0", "oneshot-fused-workaround", "postage", - "rand 0.8.5", + "rand", "retry-error", "safelog", "slotmap-careful", @@ -6073,7 +5498,7 @@ dependencies = [ "digest", "itertools 0.14.0", "paste", - "rand 0.8.5", + "rand", "safelog", "signature", "subtle", @@ -6141,8 +5566,8 @@ dependencies = [ "once_cell", "oneshot-fused-workaround", "postage", - "rand 0.8.5", - "rand_core 0.6.4", + "rand", + "rand_core", "retry-error", "safelog", "serde", @@ -6184,7 +5609,7 @@ dependencies = [ "derive_more 2.0.1", "downcast-rs", "paste", - "rand 0.8.5", + "rand", "signature", "ssh-key", "thiserror 2.0.8", @@ -6214,7 +5639,7 @@ dependencies = [ "humantime", "inventory", "itertools 0.14.0", - "rand 0.8.5", + "rand", "serde", "signature", "ssh-key", @@ -6278,7 +5703,7 @@ dependencies = [ "educe", "getrandom 0.2.15", "hex", - "rand_core 0.6.4", + "rand_core", "rsa", "safelog", "serde", @@ -6353,7 +5778,7 @@ dependencies = [ "humantime", "itertools 0.14.0", "num_enum", - "rand 0.8.5", + "rand", "serde", "static_assertions", "strum", @@ -6390,7 +5815,7 @@ dependencies = [ "itertools 0.14.0", "once_cell", "phf", - "rand 0.8.5", + "rand", "serde", "serde_with", "signature", @@ -6468,8 +5893,8 @@ dependencies = [ "hmac", "oneshot-fused-workaround", "pin-project", - "rand 0.8.5", - "rand_core 0.6.4", + "rand", + "rand_core", "safelog", "slotmap-careful", "static_assertions", @@ -6516,7 +5941,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e41754428684bd62892df2c74c2d11128cfbf3f1a8a9aaa1b920fcb90e04961a" dependencies = [ - "rand 0.8.5", + "rand", "serde", "tor-basic-utils", "tor-linkspec", @@ -6622,6 +6047,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6716,7 +6142,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "rustls", "rustls-pki-types", "sha1", @@ -6724,17 +6150,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "twox-hash" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" -dependencies = [ - "cfg-if", - "rand 0.8.5", - "static_assertions", -] - [[package]] name = "typed-index-collections" version = "3.1.0" @@ -6801,12 +6216,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-width" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -6889,11 +6298,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.1", + "js-sys", + "serde", "wasm-bindgen", ] @@ -7067,16 +6478,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webbrowser" version = "1.0.4" @@ -7123,7 +6524,6 @@ checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", - "web-sys", ] [[package]] @@ -7444,7 +6844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core 0.6.4", + "rand_core", "serde", "zeroize", ] @@ -7472,16 +6872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" -dependencies = [ - "zerocopy-derive 0.8.24", + "zerocopy-derive", ] [[package]] @@ -7495,17 +6886,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "zerocopy-derive" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "zeroize" version = "1.8.1" @@ -7525,31 +6905,3 @@ dependencies = [ "quote", "syn 2.0.90", ] - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/crates/nostr-sdk/examples/client.rs b/crates/nostr-sdk/examples/client.rs index a93406b9e..be1522539 100644 --- a/crates/nostr-sdk/examples/client.rs +++ b/crates/nostr-sdk/examples/client.rs @@ -2,6 +2,7 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license +use std::time::Duration; use nostr_sdk::prelude::*; #[tokio::main] @@ -11,9 +12,7 @@ async fn main() -> Result<()> { let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; let client = Client::new(keys); - client.add_relay("wss://relay.damus.io").await?; - client.add_relay("wss://nostr.wine").await?; - client.add_relay("wss://relay.rip").await?; + client.add_relay("ws://127.0.0.1:17445").await?; client.connect().await; @@ -23,16 +22,14 @@ async fn main() -> Result<()> { println!("Event ID: {}", output.id().to_bech32()?); println!("Sent to: {:?}", output.success); println!("Not sent to: {:?}", output.failed); - - // Create a text note POW event to relays - let builder = EventBuilder::text_note("POW text note from rust-nostr").pow(20); - client.send_event_builder(builder).await?; - - // Send a text note POW event to specific relays - let builder = EventBuilder::text_note("POW text note from rust-nostr 16").pow(16); - client - .send_event_builder_to(["wss://relay.damus.io", "wss://relay.rip"], builder) + + let events = client + .fetch_events(Filter::new().kind(Kind::TextNote), Duration::from_secs(10)) .await?; + for event in events { + println!("{}", event.as_json()) + } + Ok(()) } diff --git a/database/nostr-sqldb/Cargo.toml b/database/nostr-sqldb/Cargo.toml index 00c65bc3f..95c9787b7 100644 --- a/database/nostr-sqldb/Cargo.toml +++ b/database/nostr-sqldb/Cargo.toml @@ -11,35 +11,35 @@ readme = "README.md" rust-version.workspace = true keywords = ["nostr", "database", "postgres", "mysql", "sqlite"] +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + [features] -default = ["postgres"] -postgres = [ - "diesel/postgres", - "diesel-async/postgres", - "diesel_migrations/postgres", -] -mysql = ["diesel/mysql", "diesel-async/mysql", "diesel_migrations/mysql"] -sqlite = [ - "diesel/sqlite", - "diesel-async/sqlite", - "diesel_migrations/sqlite", - "diesel/returning_clauses_for_sqlite_3_35", -] +default = ["sqlite"] +# Enable SQLite support +sqlite = ["sqlx/sqlite"] +# Enable Postgres support +postgres = ["sqlx/postgres", "sqlx/tls-rustls-ring-webpki"] +# Enable MySQL/MariaDB support +mysql = ["sqlx/mysql", "sqlx/tls-rustls-ring-webpki"] [dependencies] -deadpool = { version = "0.12", features = ["managed", "rt_tokio_1"] } -diesel = { version = "2.2", features = ["serde_json"] } -diesel-async = { version = "0.5", features = ["deadpool"] } -diesel_migrations = "2.2" nostr = { workspace = true, features = ["std"] } nostr-database = { workspace = true, features = ["flatbuf"] } -tracing.workspace = true +sqlx = { version = "0.8", features = ["any", "migrate", "runtime-tokio"] } +tokio = { workspace = true, features = ["sync"] } [dev-dependencies] nostr-relay-builder.workspace = true +tempfile.workspace = true tokio.workspace = true tracing-subscriber.workspace = true [[example]] name = "postgres-relay" required-features = ["postgres"] + +[[example]] +name = "sqlite-relay" +required-features = ["sqlite"] diff --git a/database/nostr-sqldb/build.rs b/database/nostr-sqldb/build.rs new file mode 100644 index 000000000..d278795c9 --- /dev/null +++ b/database/nostr-sqldb/build.rs @@ -0,0 +1,7 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers +// Distributed under the MIT software license + +fn main() { + println!("cargo:rerun-if-changed=migrations"); +} \ No newline at end of file diff --git a/database/nostr-sqldb/examples/postgres-relay.rs b/database/nostr-sqldb/examples/postgres-relay.rs index 72fbfef91..b14155fb6 100644 --- a/database/nostr-sqldb/examples/postgres-relay.rs +++ b/database/nostr-sqldb/examples/postgres-relay.rs @@ -1,21 +1,28 @@ -// Copyright (c) 2025 Protom +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license + use std::time::Duration; use nostr_database::prelude::*; use nostr_relay_builder::prelude::*; -use nostr_sqldb::NostrPostgres; - -// Your database URL -const DB_URL: &str = "postgres://postgres:password@localhost:5432"; +use nostr_sqldb::{NostrSql, NostrSqlBackend}; #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); + let backend = NostrSqlBackend::Postgres { + host: String::from("localhost"), + port: 5432, + username: Some(String::from("postgres")), + password: Some(String::from("password")), + database: String::from("nostr"), + }; + // Create a nostr db instance and run pending db migrations if any - let db = NostrPostgres::new(DB_URL).await?; + let db = NostrSql::new(backend).await?; // Add db to builder let builder = RelayBuilder::default().database(db); diff --git a/database/nostr-sqldb/examples/sqlite-relay.rs b/database/nostr-sqldb/examples/sqlite-relay.rs new file mode 100644 index 000000000..4e1e3f27e --- /dev/null +++ b/database/nostr-sqldb/examples/sqlite-relay.rs @@ -0,0 +1,32 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers +// Distributed under the MIT software license + + +use std::time::Duration; + +use nostr_database::prelude::*; +use nostr_relay_builder::prelude::*; +use nostr_sqldb::{NostrSql, NostrSqlBackend}; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let backend = NostrSqlBackend::sqlite("nostr.db"); + + // Create a nostr db instance and run pending db migrations if any + let db = NostrSql::new(backend).await?; + + // Add db to builder + let builder = RelayBuilder::default().database(db); + + // Create local relay + let relay = LocalRelay::run(builder).await?; + println!("Url: {}", relay.url()); + + // Keep up the program + loop { + tokio::time::sleep(Duration::from_secs(60)).await; + } +} diff --git a/database/nostr-sqldb/migrations/.keep b/database/nostr-sqldb/migrations/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/database/nostr-sqldb/migrations/mysql/.keep b/database/nostr-sqldb/migrations/mysql/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/up.sql b/database/nostr-sqldb/migrations/mysql/001_init.sql similarity index 90% rename from database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/up.sql rename to database/nostr-sqldb/migrations/mysql/001_init.sql index ae6d82518..b59b514d2 100644 --- a/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/up.sql +++ b/database/nostr-sqldb/migrations/mysql/001_init.sql @@ -1,5 +1,5 @@ -- The actual event data -CREATE TABLE IF NOT EXISTS events ( +CREATE TABLE events ( id BLOB(32) PRIMARY KEY NOT NULL, pubkey BLOB(32) NOT NULL, created_at BIGINT NOT NULL, @@ -15,7 +15,7 @@ CREATE INDEX event_kind ON events (kind); CREATE INDEX event_deleted ON events (deleted); -- The tag index, the primary will give us the index automatically -CREATE TABLE IF NOT EXISTS event_tags ( +CREATE TABLE event_tags ( tag VARCHAR(64) NOT NULL, tag_value VARCHAR(512) NOT NULL, event_id BLOB(32) NOT NULL diff --git a/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/down.sql b/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/down.sql deleted file mode 100644 index 01cb13d3d..000000000 --- a/database/nostr-sqldb/migrations/mysql/2025-04-11-095120_events/down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- This file should undo anything in `up.sql` -DROP TABLE IF EXISTS event_tags; -DROP TABLE IF EXISTS events; diff --git a/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/down.sql b/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f526091..000000000 --- a/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/up.sql b/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index d68895b1a..000000000 --- a/database/nostr-sqldb/migrations/postgres/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; diff --git a/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/up.sql b/database/nostr-sqldb/migrations/postgres/001_init.sql similarity index 100% rename from database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/up.sql rename to database/nostr-sqldb/migrations/postgres/001_init.sql diff --git a/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/down.sql b/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/down.sql deleted file mode 100644 index ab16f6bdf..000000000 --- a/database/nostr-sqldb/migrations/postgres/2025-04-11-095120_events/down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- This file should undo anything in `up.sql` -DROP TABLE event_tags; -DROP TABLE events; diff --git a/database/nostr-sqldb/migrations/sqlite/.keep b/database/nostr-sqldb/migrations/sqlite/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/up.sql b/database/nostr-sqldb/migrations/sqlite/001_init.sql similarity index 100% rename from database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/up.sql rename to database/nostr-sqldb/migrations/sqlite/001_init.sql diff --git a/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/down.sql b/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/down.sql deleted file mode 100644 index ab16f6bdf..000000000 --- a/database/nostr-sqldb/migrations/sqlite/2025-04-11-095120_events/down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- This file should undo anything in `up.sql` -DROP TABLE event_tags; -DROP TABLE events; diff --git a/database/nostr-sqldb/mysql.toml b/database/nostr-sqldb/mysql.toml deleted file mode 100644 index 75175050b..000000000 --- a/database/nostr-sqldb/mysql.toml +++ /dev/null @@ -1,9 +0,0 @@ -# For documentation on how to configure this file, -# see https://diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/schema/mysql.rs" -custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] - -[migrations_directory] -dir = "migrations/mysql" diff --git a/database/nostr-sqldb/postgres.toml b/database/nostr-sqldb/postgres.toml deleted file mode 100644 index 285cb79ad..000000000 --- a/database/nostr-sqldb/postgres.toml +++ /dev/null @@ -1,9 +0,0 @@ -# For documentation on how to configure this file, -# see https://diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/schema/postgres.rs" -custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] - -[migrations_directory] -dir = "migrations/postgres" diff --git a/database/nostr-sqldb/sqlite.toml b/database/nostr-sqldb/sqlite.toml deleted file mode 100644 index e1c7f21c0..000000000 --- a/database/nostr-sqldb/sqlite.toml +++ /dev/null @@ -1,9 +0,0 @@ -# For documentation on how to configure this file, -# see https://diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/schema/sqlite.rs" -custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] - -[migrations_directory] -dir = "migrations/sqlite" diff --git a/database/nostr-sqldb/src/db.rs b/database/nostr-sqldb/src/db.rs new file mode 100644 index 000000000..feb65b1c1 --- /dev/null +++ b/database/nostr-sqldb/src/db.rs @@ -0,0 +1,756 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers +// Distributed under the MIT software license + +//! Nostr SQL + +use std::borrow::Cow; +use std::fmt; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +#[cfg(feature = "sqlite")] +use sqlx::sqlite::Sqlite; +use sqlx::{AnyPool, Any, Transaction, QueryBuilder}; +use sqlx::migrate::MigrateDatabase; +use nostr_database::prelude::*; +use tokio::sync::Mutex; + +use crate::error::Error; +use crate::model::{EventDataDb, EventDb, EventTagDb}; + +const EVENTS_QUERY_LIMIT: usize = 10_000; + +/// SQL backend +pub enum NostrSqlBackend { + /// SQLite + #[cfg(feature = "sqlite")] + Sqlite { + /// SQLite database path + /// + /// If no path is passed, an in-memory database will be created. + path: Option + }, + /// Postgres + #[cfg(feature = "postgres")] + Postgres { + /// Host + host: String, + /// Port + port: u16, + /// Username + username: Option, + /// Password + password: Option, + /// Database name + database: String, + } +} + +impl NostrSqlBackend { + /// New persistent SQLite database + #[inline] + #[cfg(feature = "sqlite")] + pub fn sqlite

(path: P) -> Self + where + P: AsRef + { + Self::Sqlite { path: Some(path.as_ref().to_path_buf()) } + } + + /// New in-memory SQLite database + #[inline] + #[cfg(feature = "sqlite")] + pub fn sqlite_memory() -> Self { + Self::Sqlite { path: None } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum PoolKind { + #[cfg(feature = "sqlite")] + Sqlite, + #[cfg(feature = "postgres")] + Postgres, + #[cfg(feature = "mysql")] + MySql, +} + +/// Nostr SQL database +#[derive(Clone)] +pub struct NostrSql { + pool: AnyPool, + kind: PoolKind, + fbb: Arc>>, +} + +impl fmt::Debug for NostrSql { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NostrSql") + .field("pool", &self.pool) + .field("kind", &self.kind) + .finish() + } +} + +impl NostrSql { + /// Connect to a SQL database + pub async fn new(backend: NostrSqlBackend) -> Result { + // Install drivers + sqlx::any::install_default_drivers(); + + let (pool, kind) = match backend { + #[cfg(feature = "sqlite")] + NostrSqlBackend::Sqlite {path} => { + let uri: Cow = match path { + Some(path) => Cow::Owned(format!("sqlite://{}", path.display())), + None => Cow::Borrowed("sqlite:memory:"), + }; + + if !Sqlite::database_exists(&uri).await? { + Sqlite::create_database(&uri).await?; + } + + let pool: AnyPool = AnyPool::connect(&uri).await?; + + sqlx::migrate!("migrations/sqlite").run(&pool).await?; + + (pool, PoolKind::Sqlite) + } + #[cfg(feature = "postgres")] + NostrSqlBackend::Postgres {host, port, username, password, database } => { + let uri: String = match (username, password) { + (Some(username), Some(password)) => format!("postgres://{username}:{password}@{host}:{port}/{database}"), + _ => format!("postgres://{host}:{port}/{database}") + }; + + let pool: AnyPool = AnyPool::connect(&uri).await?; + + sqlx::migrate!("migrations/postgres").run(&pool).await?; + + (pool, PoolKind::Postgres) + } + }; + + Ok(Self { + pool, + kind, + fbb: Arc::new(Mutex::new(FlatBufferBuilder::new())), + }) + } + + /// Returns true if successfully inserted + async fn insert_event_tx(&self, tx: &mut Transaction<'_, Any>, event: &EventDb) -> Result { + let sql: &str = match self.kind { + #[cfg(feature = "sqlite")] + PoolKind::Sqlite => { + "INSERT OR IGNORE INTO events (id, pubkey, created_at, kind, payload, deleted) VALUES (?, ?, ?, ?, ?, ?)" + }, + #[cfg(feature = "postgres")] + PoolKind::Postgres => { + "INSERT INTO events (id, pubkey, created_at, kind, payload, deleted) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO NOTHING" + }, + #[cfg(feature = "mysql")] + PoolKind::MySql => { + "INSERT IGNORE INTO events (id, pubkey, created_at, kind, payload, deleted) VALUES (?, ?, ?, ?, ?, ?)" + }, + }; + + let result = sqlx::query(sql) + .bind(&event.id) + .bind(&event.pubkey) + .bind(event.created_at) + .bind(event.kind) + .bind(&event.payload) + .bind(event.deleted) + .execute(&mut **tx) + .await?; + + Ok(result.rows_affected() > 0) + } + + async fn insert_tags_tx(&self, tx: &mut Transaction<'_, Any>, tags: &[EventTagDb]) -> Result<(), Error> { + let sql: &str = match self.kind { + #[cfg(feature = "sqlite")] + PoolKind::Sqlite => { + "INSERT OR IGNORE INTO event_tags (tag, tag_value, event_id) VALUES (?, ?, ?)" + }, + #[cfg(feature = "postgres")] + PoolKind::Postgres => { + "INSERT INTO event_tags (tag, tag_value, event_id) VALUES (?, ?, ?) ON CONFLICT (tag, tag_value, event_id) DO NOTHING" + }, + #[cfg(feature = "mysql")] + PoolKind::MySql => { + "INSERT IGNORE INTO event_tags (tag, tag_value, event_id) VALUES (?, ?, ?)" + }, + }; + + for tag in tags { + sqlx::query(sql) + .bind(&tag.tag) + .bind(&tag.tag_value) + .bind(&tag.event_id) + .execute(&mut **tx) + .await?; + } + + Ok(()) + } + + + async fn _save_event(&self, event: &Event) -> Result { + if event.kind.is_ephemeral() { + return Ok(SaveEventStatus::Rejected(RejectedReason::Ephemeral)); + } + + let mut tx = self.pool.begin().await?; + + // Convert event + let data: EventDataDb = { + let mut fbb = self.fbb.lock().await; + EventDataDb::from_event(event, &mut fbb) + }; + + // TODO: check if event is deleted + // TODO: check if is replaced + + // Insert event first + let inserted: bool = self.insert_event_tx(&mut tx, &data.event).await?; + + // Check if the event has been inserted + if inserted { + // Insert tags + if !data.tags.is_empty() { + self.insert_tags_tx(&mut tx, &data.tags).await?; + } + + // Commit transaction + tx.commit().await?; + + Ok(SaveEventStatus::Success) + } else { + // Event has not been inserted, rollback transaction + tx.rollback().await?; + Ok(SaveEventStatus::Rejected(RejectedReason::Duplicate)) + } + } + + async fn get_event_by_id(&self, id: &EventId) -> Result, Error> { + let query = sqlx::query_as::("SELECT * FROM events WHERE id = ?") + .bind(id.as_bytes().to_vec()); + Ok(query.fetch_optional(&self.pool).await?) + } +} + +impl NostrDatabase for NostrSql { + fn backend(&self) -> Backend { + match self.kind { + #[cfg(feature = "sqlite")] + PoolKind::Sqlite => Backend::SQLite, + #[cfg(feature = "postgres")] + PoolKind::Postgres => Backend::Custom(String::from("Postgres")), + #[cfg(feature = "mysql")] + PoolKind::MySql => Backend::Custom(String::from("MySQL")), + } + } + + fn save_event<'a>( + &'a self, + event: &'a Event, + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { + self._save_event(event).await.map_err(DatabaseError::backend) + }) + } + + fn check_id<'a>( + &'a self, + event_id: &'a EventId, + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { + match self.get_event_by_id(event_id).await.map_err(DatabaseError::backend)? { + Some(e) if e.deleted => Ok(DatabaseEventStatus::Deleted), + Some(_) => Ok(DatabaseEventStatus::Saved), + None => Ok(DatabaseEventStatus::NotExistent), + } + }) + } + + fn event_by_id<'a>( + &'a self, + event_id: &'a EventId, + ) -> BoxedFuture<'a, Result, DatabaseError>> { + Box::pin(async move { + match self.get_event_by_id(event_id).await.map_err(DatabaseError::backend)? { + Some(e) if !e.deleted => { + Ok(Some(Event::decode(&e.payload).map_err(DatabaseError::backend)?)) + } + _ => Ok(None), + } + }) + } + + fn count(&self, filter: Filter) -> BoxedFuture> { + Box::pin(async move { + Ok(self.query(filter).await?.len()) + }) + } + + fn query(&self, filter: Filter) -> BoxedFuture> { + Box::pin(async move { + // Limit filter query + let filter: Filter = with_limit(filter, EVENTS_QUERY_LIMIT); + + let mut events: Events = Events::new(&filter); + + let sql = build_filter_query(filter); + + let payloads: Vec<(Vec,)> = sqlx::query_as(&sql).fetch_all(&self.pool).await.map_err(DatabaseError::backend)?; + + for (payload,) in payloads.into_iter() { + if let Ok(event) = Event::decode(&payload) { + events.insert(event); + } + } + Ok(events) + }) + } + + fn delete(&self, _filter: Filter) -> BoxedFuture> { + // Box::pin(async move { + // let filter = with_limit(filter, 999); + // let filter = build_filter_query(filter); + // diesel::update(events::table) + // .set(events::deleted.eq(true)) + // .filter(events::id.eq_any(filter.select(events::id))) + // .execute(&mut self.get_connection().await?) + // .await + // .map_err(DatabaseError::backend)?; + // + // Ok(()) + // }) + Box::pin(async move { Err(DatabaseError::NotSupported )}) + } + + fn wipe(&self) -> BoxedFuture> { + Box::pin(async move { Err(DatabaseError::NotSupported )}) + } +} + +fn build_filter_query(filter: Filter) -> String { + let mut query_builder: QueryBuilder = QueryBuilder::new( + "SELECT DISTINCT e.payload + FROM events e + INNER JOIN event_tags et ON e.id = et.event_id + WHERE e.deleted = 0" + ); + + // Add filters + if let Some(ids) = filter.ids { + if !ids.is_empty() { + query_builder.push(" AND e.id IN ("); + let mut separated = query_builder.separated(", "); + for id in ids.into_iter() { + separated.push_bind(id.as_bytes().to_vec()); + } + query_builder.push(")"); + } + } + + if let Some(authors) = filter.authors { + if !authors.is_empty() { + query_builder.push(" AND e.pubkey IN ("); + let mut separated = query_builder.separated(", "); + for author in authors { + separated.push_bind(author.as_bytes().to_vec()); + } + query_builder.push(")"); + } + } + + if let Some(kinds) = filter.kinds { + if !kinds.is_empty() { + query_builder.push(" AND e.kind IN ("); + let mut separated = query_builder.separated(", "); + for kind in kinds { + separated.push_bind(kind.as_u16() as i64); + } + query_builder.push(")"); + } + } + + if let Some(since) = filter.since { + query_builder.push(" AND e.created_at >= "); + query_builder.push_bind(since.as_u64() as i64); + } + + if let Some(until) = filter.until { + query_builder.push(" AND e.created_at <= "); + query_builder.push_bind(until.as_u64() as i64); + } + + if !filter.generic_tags.is_empty() { + for (tag, values) in filter.generic_tags { + if !values.is_empty() { + query_builder.push(" AND EXISTS ( + SELECT 1 FROM event_tags et2 + WHERE et2.event_id = e.id + AND et2.tag = "); + query_builder.push_bind(tag.to_string()); + query_builder.push(" AND et2.tag_value IN ("); + + let mut separated = query_builder.separated(", "); + for value in values { + separated.push_bind(value.to_string()); + } + query_builder.push("))"); + } + } + } + + query_builder.push(" ORDER BY e.created_at DESC"); + + if let Some(limit) = filter.limit { + query_builder.push(" LIMIT "); + query_builder.push_bind(limit as i64); + } + + query_builder.into_sql() +} + +/// sets the given default limit on a Nostr filter if not set +fn with_limit(filter: Filter, default_limit: usize) -> Filter { + if filter.limit.is_none() { + return filter.limit(default_limit); + } + filter +} + +#[cfg(test)] +mod tests { + use std::ops::Deref; + use std::time::Duration; + + use tempfile::TempDir; + + use super::*; + + const EVENTS: [&str; 14] = [ + r#"{"id":"b7b1fb52ad8461a03e949820ae29a9ea07e35bcd79c95c4b59b0254944f62805","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644581,"kind":1,"tags":[],"content":"Text note","sig":"ed73a8a4e7c26cd797a7b875c634d9ecb6958c57733305fed23b978109d0411d21b3e182cb67c8ad750884e30ca383b509382ae6187b36e76ee76e6a142c4284"}"#, + r#"{"id":"7296747d91c53f1d71778ef3e12d18b66d494a41f688ef244d518abf37c959b6","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644586,"kind":32121,"tags":[["d","id-1"]],"content":"Empty 1","sig":"8848989a8e808f7315e950f871b231c1dff7752048f8957d4a541881d2005506c30e85c7dd74dab022b3e01329c88e69c9d5d55d961759272a738d150b7dbefc"}"#, + r#"{"id":"ec6ea04ba483871062d79f78927df7979f67545b53f552e47626cb1105590442","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644591,"kind":32122,"tags":[["d","id-1"]],"content":"Empty 2","sig":"89946113a97484850fe35fefdb9120df847b305de1216dae566616fe453565e8707a4da7e68843b560fa22a932f81fc8db2b5a2acb4dcfd3caba9a91320aac92"}"#, + r#"{"id":"63b8b829aa31a2de870c3a713541658fcc0187be93af2032ec2ca039befd3f70","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644596,"kind":32122,"tags":[["d","id-2"]],"content":"","sig":"607b1a67bef57e48d17df4e145718d10b9df51831d1272c149f2ab5ad4993ae723f10a81be2403ae21b2793c8ed4c129e8b031e8b240c6c90c9e6d32f62d26ff"}"#, + r#"{"id":"6fe9119c7db13ae13e8ecfcdd2e5bf98e2940ba56a2ce0c3e8fba3d88cd8e69d","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644601,"kind":32122,"tags":[["d","id-3"]],"content":"","sig":"d07146547a726fc9b4ec8d67bbbe690347d43dadfe5d9890a428626d38c617c52e6945f2b7144c4e0c51d1e2b0be020614a5cadc9c0256b2e28069b70d9fc26e"}"#, + r#"{"id":"a82f6ebfc709f4e7c7971e6bf738e30a3bc112cfdb21336054711e6779fd49ef","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644606,"kind":32122,"tags":[["d","id-1"]],"content":"","sig":"96d3349b42ed637712b4d07f037457ab6e9180d58857df77eb5fa27ff1fd68445c72122ec53870831ada8a4d9a0b484435f80d3ff21a862238da7a723a0d073c"}"#, + r#"{"id":"8ab0cb1beceeb68f080ec11a3920b8cc491ecc7ec5250405e88691d733185832","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644611,"kind":32122,"tags":[["d","id-1"]],"content":"Test","sig":"49153b482d7110e2538eb48005f1149622247479b1c0057d902df931d5cea105869deeae908e4e3b903e3140632dc780b3f10344805eab77bb54fb79c4e4359d"}"#, + r#"{"id":"63dc49a8f3278a2de8dc0138939de56d392b8eb7a18c627e4d78789e2b0b09f2","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644616,"kind":5,"tags":[["a","32122:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:"]],"content":"","sig":"977e54e5d57d1fbb83615d3a870037d9eb5182a679ca8357523bbf032580689cf481f76c88c7027034cfaf567ba9d9fe25fc8cd334139a0117ad5cf9fe325eef"}"#, + r#"{"id":"6975ace0f3d66967f330d4758fbbf45517d41130e2639b54ca5142f37757c9eb","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644621,"kind":5,"tags":[["a","32122:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:id-2"]],"content":"","sig":"9bb09e4759899d86e447c3fa1be83905fe2eda74a5068a909965ac14fcdabaed64edaeb732154dab734ca41f2fc4d63687870e6f8e56e3d9e180e4a2dd6fb2d2"}"#, + r#"{"id":"33f5b4e6a38e107638c20f4536db35191d4b8651ba5a2cefec983b9ec2d65084","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704645586,"kind":0,"tags":[],"content":"{\"name\":\"Key A\"}","sig":"285d090f45a6adcae717b33771149f7840a8c27fb29025d63f1ab8d95614034a54e9f4f29cee9527c4c93321a7ebff287387b7a19ba8e6f764512a40e7120429"}"#, + r#"{"id":"90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704645606,"kind":0,"tags":[],"content":"{\"name\":\"key-a\",\"display_name\":\"Key A\",\"lud16\":\"keya@ln.address\"}","sig":"ec8f49d4c722b7ccae102d49befff08e62db775e5da43ef51b25c47dfdd6a09dc7519310a3a63cbdb6ec6b3250e6f19518eb47be604edeb598d16cdc071d3dbc"}"#, + r#"{"id":"a295422c636d3532875b75739e8dae3cdb4dd2679c6e4994c9a39c7ebf8bc620","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704646569,"kind":5,"tags":[["e","90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9"]],"content":"","sig":"d4dc8368a4ad27eef63cacf667345aadd9617001537497108234fc1686d546c949cbb58e007a4d4b632c65ea135af4fbd7a089cc60ab89b6901f5c3fc6a47b29"}"#, // Invalid event deletion + r#"{"id":"999e3e270100d7e1eaa98fcfab4a98274872c1f2dfdab024f32e42a5a12d5b5e","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704646606,"kind":5,"tags":[["e","90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9"]],"content":"","sig":"4f3a33fd52784cea7ca8428fd35d94d65049712e9aa11a70b1a16a1fcd761c7b7e27afac325728b1c00dfa11e33e78b2efd0430a7e4b28f4ede5b579b3f32614"}"#, + r#"{"id":"99a022e6d61c4e39c147d08a2be943b664e8030c0049325555ac1766429c2832","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1705241093,"kind":30333,"tags":[["d","multi-id"],["p","aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4"]],"content":"Multi-tags","sig":"0abfb2b696a7ed7c9e8e3bf7743686190f3f1b3d4045b72833ab6187c254f7ed278d289d52dfac3de28be861c1471421d9b1bfb5877413cbc81c84f63207a826"}"#, + ]; + + struct TempDatabase { + db: NostrSql, + // Needed to avoid the drop and deletion of temp folder + _temp: TempDir, + } + + impl Deref for TempDatabase { + type Target = NostrSql; + + fn deref(&self) -> &Self::Target { + &self.db + } + } + + impl TempDatabase { + async fn new() -> Self { + let path = tempfile::tempdir().unwrap(); + let backend = NostrSqlBackend::sqlite(path.path().join("temp.db")); + Self { + db: NostrSql::new(backend).await.unwrap(), + _temp: path, + } + } + + // Return the number of added events + async fn add_random_events(&self) -> usize { + let keys_a = Keys::generate(); + let keys_b = Keys::generate(); + + let events = vec![ + EventBuilder::text_note("Text Note A") + .sign_with_keys(&keys_a) + .unwrap(), + EventBuilder::text_note("Text Note B") + .sign_with_keys(&keys_b) + .unwrap(), + EventBuilder::metadata( + &Metadata::new().name("account-a").display_name("Account A"), + ) + .sign_with_keys(&keys_a) + .unwrap(), + EventBuilder::metadata( + &Metadata::new().name("account-b").display_name("Account B"), + ) + .sign_with_keys(&keys_b) + .unwrap(), + EventBuilder::new(Kind::Custom(33_333), "") + .tag(Tag::identifier("my-id-a")) + .sign_with_keys(&keys_a) + .unwrap(), + EventBuilder::new(Kind::Custom(33_333), "") + .tag(Tag::identifier("my-id-b")) + .sign_with_keys(&keys_b) + .unwrap(), + ]; + + // Store + for event in events.iter() { + self.db.save_event(event).await.unwrap(); + } + + events.len() + } + + async fn add_event(&self, builder: EventBuilder) -> (Keys, Event) { + let keys = Keys::generate(); + let event = builder.sign_with_keys(&keys).unwrap(); + self.db.save_event(&event).await.unwrap(); + (keys, event) + } + + async fn add_event_with_keys( + &self, + builder: EventBuilder, + keys: &Keys, + ) -> (Event, SaveEventStatus) { + let event = builder.sign_with_keys(keys).unwrap(); + let status = self.db.save_event(&event).await.unwrap(); + (event, status) + } + + async fn count_all(&self) -> usize { + self.db.count(Filter::new()).await.unwrap() + } + } + + #[tokio::test] + async fn test_event_by_id() { + let db = TempDatabase::new().await; + + let added_events: usize = db.add_random_events().await; + + let (_keys, expected_event) = db.add_event(EventBuilder::text_note("Test")).await; + + let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap(); + assert_eq!(event, expected_event); + + // Check if number of events in database match the expected + assert_eq!(db.count_all().await, added_events + 1) + } + + #[tokio::test] + async fn test_replaceable_event() { + let db = TempDatabase::new().await; + + let added_events: usize = db.add_random_events().await; + + let now = Timestamp::now(); + let metadata = Metadata::new() + .name("my-account") + .display_name("My Account"); + + let (keys, expected_event) = db + .add_event( + EventBuilder::metadata(&metadata).custom_created_at(now - Duration::from_secs(120)), + ) + .await; + + // Test event by ID + let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap(); + assert_eq!(event, expected_event); + + // Test filter query + let events = db + .query(Filter::new().author(keys.public_key).kind(Kind::Metadata)) + .await + .unwrap(); + assert_eq!(events.to_vec(), vec![expected_event.clone()]); + + // Check if number of events in database match the expected + assert_eq!(db.count_all().await, added_events + 1); + + // Replace previous event + let (new_expected_event, status) = db + .add_event_with_keys( + EventBuilder::metadata(&metadata).custom_created_at(now), + &keys, + ) + .await; + assert!(status.is_success()); + + // Test event by ID (MUST be None because replaced) + assert!(db.event_by_id(&expected_event.id).await.unwrap().is_none()); + + // Test event by ID + let event = db + .event_by_id(&new_expected_event.id) + .await + .unwrap() + .unwrap(); + assert_eq!(event, new_expected_event); + + // Test filter query + let events = db + .query(Filter::new().author(keys.public_key).kind(Kind::Metadata)) + .await + .unwrap(); + assert_eq!(events.to_vec(), vec![new_expected_event]); + + // Check if number of events in database match the expected + assert_eq!(db.count_all().await, added_events + 1); + } + + #[tokio::test] + async fn test_param_replaceable_event() { + let db = TempDatabase::new().await; + + let added_events: usize = db.add_random_events().await; + + let now = Timestamp::now(); + + let (keys, expected_event) = db + .add_event( + EventBuilder::new(Kind::Custom(33_333), "") + .tag(Tag::identifier("my-id-a")) + .custom_created_at(now - Duration::from_secs(120)), + ) + .await; + let coordinate = Coordinate::new(Kind::from(33_333), keys.public_key).identifier("my-id-a"); + + // Test event by ID + let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap(); + assert_eq!(event, expected_event); + + // Test filter query + let events = db.query(coordinate.clone().into()).await.unwrap(); + assert_eq!(events.to_vec(), vec![expected_event.clone()]); + + // Check if number of events in database match the expected + assert_eq!(db.count_all().await, added_events + 1); + + // Replace previous event + let (new_expected_event, status) = db + .add_event_with_keys( + EventBuilder::new(Kind::Custom(33_333), "Test replace") + .tag(Tag::identifier("my-id-a")) + .custom_created_at(now), + &keys, + ) + .await; + assert!(status.is_success()); + + // Test event by ID (MUST be None` because replaced) + assert!(db.event_by_id(&expected_event.id).await.unwrap().is_none()); + + // Test event by ID + let event = db + .event_by_id(&new_expected_event.id) + .await + .unwrap() + .unwrap(); + assert_eq!(event, new_expected_event); + + // Test filter query + let events = db.query(coordinate.into()).await.unwrap(); + assert_eq!(events.to_vec(), vec![new_expected_event]); + + // Check if number of events in database match the expected + assert_eq!(db.count_all().await, added_events + 1); + + // Trey to add param replaceable event with older timestamp (MUSTN'T be stored) + let (_, status) = db + .add_event_with_keys( + EventBuilder::new(Kind::Custom(33_333), "Test replace 2") + .tag(Tag::identifier("my-id-a")) + .custom_created_at(now - Duration::from_secs(2000)), + &keys, + ) + .await; + assert!(!status.is_success()); + } + + #[tokio::test] + async fn test_full_text_search() { + let db = TempDatabase::new().await; + + let _added_events: usize = db.add_random_events().await; + + let events = db.query(Filter::new().search("Account A")).await.unwrap(); + assert_eq!(events.len(), 1); + + let events = db.query(Filter::new().search("account a")).await.unwrap(); + assert_eq!(events.len(), 1); + + let events = db.query(Filter::new().search("text note")).await.unwrap(); + assert_eq!(events.len(), 2); + + let events = db.query(Filter::new().search("notes")).await.unwrap(); + assert_eq!(events.len(), 0); + + let events = db.query(Filter::new().search("hola")).await.unwrap(); + assert_eq!(events.len(), 0); + } + + #[tokio::test] + async fn test_expected_query_result() { + let db = TempDatabase::new().await; + + for event in EVENTS.into_iter() { + let event = Event::from_json(event).unwrap(); + let _ = db.save_event(&event).await; + } + + // Test expected output + let expected_output = vec![ + Event::from_json(EVENTS[13]).unwrap(), + Event::from_json(EVENTS[12]).unwrap(), + // Event 11 is invalid deletion + // Event 10 deleted by event 12 + // Event 9 replaced by event 10 + Event::from_json(EVENTS[8]).unwrap(), + // Event 7 is an invalid deletion + Event::from_json(EVENTS[6]).unwrap(), + Event::from_json(EVENTS[5]).unwrap(), + Event::from_json(EVENTS[4]).unwrap(), + // Event 3 deleted by Event 8 + // Event 2 replaced by Event 6 + Event::from_json(EVENTS[1]).unwrap(), + Event::from_json(EVENTS[0]).unwrap(), + ]; + assert_eq!( + db.query(Filter::new()).await.unwrap().to_vec(), + expected_output + ); + assert_eq!(db.count_all().await, 8); + } + + #[tokio::test] + async fn test_delete_events_with_filter() { + let db = TempDatabase::new().await; + + let added_events: usize = db.add_random_events().await; + + assert_eq!(db.count_all().await, added_events); + + // Delete all kinds except text note + let filter = Filter::new().kinds([Kind::Metadata, Kind::Custom(33_333)]); + db.delete(filter).await.unwrap(); + + assert_eq!(db.count_all().await, 2); + } +} diff --git a/database/nostr-sqldb/src/error.rs b/database/nostr-sqldb/src/error.rs new file mode 100644 index 000000000..a1761bb3a --- /dev/null +++ b/database/nostr-sqldb/src/error.rs @@ -0,0 +1,41 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers +// Distributed under the MIT software license + +//! Error + +use std::fmt; + +use sqlx::migrate::MigrateError; + +/// Nostr SQL error +#[derive(Debug)] +pub enum Error { + /// SQLx error + Sqlx(sqlx::Error), + /// Migration error + Migrate(MigrateError), +} + +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Sqlx(e) => write!(f, "{e}"), + Self::Migrate(e) => write!(f, "{e}"), + } + } +} + +impl From for Error { + fn from(e: sqlx::Error) -> Self { + Self::Sqlx(e) + } +} + +impl From for Error { + fn from(e: MigrateError) -> Self { + Self::Migrate(e) + } +} \ No newline at end of file diff --git a/database/nostr-sqldb/src/lib.rs b/database/nostr-sqldb/src/lib.rs index 7c464c62e..ca843e645 100644 --- a/database/nostr-sqldb/src/lib.rs +++ b/database/nostr-sqldb/src/lib.rs @@ -1,23 +1,20 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers +// Distributed under the MIT software license + +//! Nostr SQL database + #![forbid(unsafe_code)] #![warn(missing_docs)] #![warn(rustdoc::bare_urls)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] -mod migrations; -#[allow(dead_code)] // TODO: to remove when also SQLite and MySQL are implemented +#[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))] +compile_error!("At least one database backend must be enabled"); + mod model; -#[cfg(feature = "postgres")] -mod postgres; -#[allow(dead_code)] // TODO: to remove when also SQLite and MySQL are implemented -mod query; -mod schema; +pub mod error; +pub mod db; -#[cfg(feature = "mysql")] -pub use migrations::mysql::run_migrations; -#[cfg(feature = "postgres")] -pub use migrations::postgres::run_migrations; -#[cfg(feature = "sqlite")] -pub use migrations::sqlite::run_migrations; -#[cfg(feature = "postgres")] -pub use postgres::{postgres_connection_pool, NostrPostgres}; +pub use self::db::{NostrSql, NostrSqlBackend}; \ No newline at end of file diff --git a/database/nostr-sqldb/src/migrations/mod.rs b/database/nostr-sqldb/src/migrations/mod.rs deleted file mode 100644 index 13c12d36d..000000000 --- a/database/nostr-sqldb/src/migrations/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[cfg(feature = "mysql")] -pub mod mysql; - -#[cfg(feature = "postgres")] -pub mod postgres; - -#[cfg(feature = "sqlite")] -pub mod sqlite; diff --git a/database/nostr-sqldb/src/migrations/mysql.rs b/database/nostr-sqldb/src/migrations/mysql.rs deleted file mode 100644 index ae3f171c8..000000000 --- a/database/nostr-sqldb/src/migrations/mysql.rs +++ /dev/null @@ -1,19 +0,0 @@ -use diesel::{Connection, MysqlConnection}; -use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use nostr_database::DatabaseError; -use tracing::info; - -const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/mysql"); - -/// programatically run the db migrations -pub fn run_migrations(connection_string: &str) -> Result<(), DatabaseError> { - info!("Running db migrations in mysql database",); - let mut connection = - MysqlConnection::establish(connection_string).map_err(DatabaseError::backend)?; - - let res = connection - .run_pending_migrations(MIGRATIONS) - .map_err(DatabaseError::Backend)?; - info!("Successfully executed mysql db migrations {:?}", res); - Ok(()) -} diff --git a/database/nostr-sqldb/src/migrations/postgres.rs b/database/nostr-sqldb/src/migrations/postgres.rs deleted file mode 100644 index f54b32080..000000000 --- a/database/nostr-sqldb/src/migrations/postgres.rs +++ /dev/null @@ -1,19 +0,0 @@ -use diesel::{Connection, PgConnection}; -use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use nostr_database::DatabaseError; -use tracing::info; - -const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/postgres"); - -/// programatically run the db migrations -pub fn run_migrations(connection_string: &str) -> Result<(), DatabaseError> { - info!("Running db migrations in postgres database",); - let mut connection = - PgConnection::establish(connection_string).map_err(DatabaseError::backend)?; - - let res = connection - .run_pending_migrations(MIGRATIONS) - .map_err(DatabaseError::Backend)?; - info!("Successfully executed postgres db migrations {:?}", res); - Ok(()) -} diff --git a/database/nostr-sqldb/src/migrations/sqlite.rs b/database/nostr-sqldb/src/migrations/sqlite.rs deleted file mode 100644 index 34a3e943d..000000000 --- a/database/nostr-sqldb/src/migrations/sqlite.rs +++ /dev/null @@ -1,19 +0,0 @@ -use diesel::{Connection, SqliteConnection}; -use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use nostr_database::DatabaseError; -use tracing::info; - -const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations/sqlite"); - -/// programatically run the db migrations -pub fn run_migrations(connection_string: &str) -> Result<(), DatabaseError> { - info!("Running db migrations in sqlite database",); - let mut connection = - SqliteConnection::establish(connection_string).map_err(DatabaseError::backend)?; - - let res = connection - .run_pending_migrations(MIGRATIONS) - .map_err(DatabaseError::Backend)?; - info!("Successfully executed sqlite db migrations {:?}", res); - Ok(()) -} diff --git a/database/nostr-sqldb/src/model.rs b/database/nostr-sqldb/src/model.rs index c1fec936b..46ca9e6f5 100644 --- a/database/nostr-sqldb/src/model.rs +++ b/database/nostr-sqldb/src/model.rs @@ -1,20 +1,13 @@ -use std::sync::{Mutex, OnceLock}; +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2025 Rust Nostr Developers +// Distributed under the MIT software license -use diesel::prelude::*; +use sqlx::FromRow; use nostr::event::Event; -use nostr_database::{DatabaseError, FlatBufferBuilder, FlatBufferEncode}; +use nostr_database::{FlatBufferBuilder, FlatBufferEncode}; -#[cfg(feature = "mysql")] -use crate::schema::mysql::{event_tags, events}; -#[cfg(feature = "postgres")] -use crate::schema::postgres::{event_tags, events}; -#[cfg(feature = "sqlite")] -use crate::schema::sqlite::{event_tags, events}; - -/// DB representation of [`Event`] -#[derive(Queryable, Selectable, Insertable, AsChangeset, Debug, Clone)] -#[diesel(table_name = events)] -pub struct EventDb { +#[derive(Debug, Clone, FromRow)] +pub(crate) struct EventDb { pub id: Vec, pub pubkey: Vec, pub created_at: i64, @@ -23,10 +16,8 @@ pub struct EventDb { pub deleted: bool, } -/// DB representation of [`EventTag`] -#[derive(Queryable, Selectable, Insertable, AsChangeset, Debug, Clone)] -#[diesel(table_name = event_tags)] -pub struct EventTagDb { +#[derive(Debug, Clone, FromRow)] +pub(crate) struct EventTagDb { pub tag: String, pub tag_value: String, pub event_id: Vec, @@ -34,36 +25,24 @@ pub struct EventTagDb { /// A data container for extracting data from [`Event`] and its tags #[derive(Debug, Clone)] -pub struct EventDataDb { +pub(crate) struct EventDataDb { pub event: EventDb, pub tags: Vec, } -impl TryFrom<&Event> for EventDataDb { - type Error = DatabaseError; - fn try_from(value: &Event) -> Result { - Ok(Self { +impl EventDataDb { + pub(crate) fn from_event(event: &Event, fbb: &mut FlatBufferBuilder) -> Self { + Self { event: EventDb { - id: value.id.as_bytes().to_vec(), - pubkey: value.pubkey.as_bytes().to_vec(), - created_at: value.created_at.as_u64() as i64, - kind: value.kind.as_u16() as i64, - payload: encode_payload(value), + id: event.id.as_bytes().to_vec(), + pubkey: event.pubkey.as_bytes().to_vec(), + created_at: event.created_at.as_u64() as i64, + kind: event.kind.as_u16() as i64, + payload: event.encode(fbb).to_vec(), deleted: false, }, - tags: extract_tags(value), - }) - } -} - -fn encode_payload(value: &Event) -> Vec { - static FB_BUILDER: OnceLock> = OnceLock::new(); - match FB_BUILDER - .get_or_init(|| Mutex::new(FlatBufferBuilder::new())) - .lock() - { - Ok(mut fb_builder) => value.encode(&mut fb_builder).to_vec(), - Err(_) => value.encode(&mut FlatBufferBuilder::new()).to_vec(), + tags: extract_tags(event), + } } } diff --git a/database/nostr-sqldb/src/postgres.rs b/database/nostr-sqldb/src/postgres.rs deleted file mode 100644 index 257ae49d3..000000000 --- a/database/nostr-sqldb/src/postgres.rs +++ /dev/null @@ -1,235 +0,0 @@ -use deadpool::managed::{Object, Pool}; -use diesel::prelude::*; -use diesel::result::{DatabaseErrorKind, Error as DieselError}; -use diesel::QueryResult; -use diesel_async::pooled_connection::AsyncDieselConnectionManager; -use diesel_async::scoped_futures::ScopedFutureExt; -use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; -use nostr::event::*; -use nostr::filter::Filter; -use nostr::types::Timestamp; -use nostr_database::*; -use prelude::BoxedFuture; - -use super::model::{EventDataDb, EventDb}; -use super::schema::postgres::{event_tags, events}; -use crate::query::{build_filter_query, event_by_id, with_limit}; - -/// Shorthand for a database connection pool type -pub type PostgresConnectionPool = Pool>; -pub type PostgresConnection = Object>; - -#[derive(Clone)] -pub struct NostrPostgres { - pool: PostgresConnectionPool, -} - -impl NostrPostgres { - /// Create a new [`NostrPostgres`] instance - pub async fn new(connection_string: C) -> Result - where - C: AsRef, - { - crate::migrations::postgres::run_migrations(connection_string.as_ref())?; - let pool = postgres_connection_pool(connection_string).await?; - Ok(Self { pool }) - } - - pub(crate) async fn get_connection(&self) -> Result { - self.pool.get().await.map_err(DatabaseError::backend) - } - - pub(crate) async fn save( - &self, - event_data: EventDataDb, - ) -> Result { - let mut db = self.get_connection().await?; - let result: QueryResult = db - .transaction(|c| { - async move { - diesel::insert_into(events::table) - .values(&event_data.event) - .execute(c) - .await?; - - diesel::insert_into(event_tags::table) - .values(&event_data.tags) - .execute(c) - .await?; - - Ok(true) - } - .scope_boxed() - }) - .await; - - match result { - Ok(_) => Ok(SaveEventStatus::Success), - Err(e) => match e { - DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _) => { - Ok(SaveEventStatus::Rejected(RejectedReason::Duplicate)) - } - e => Err(DatabaseError::backend(e)), - }, - } - } - - pub(crate) async fn event_by_id( - &self, - event_id: &EventId, - ) -> Result, DatabaseError> { - let res = event_by_id(event_id) - .first(&mut self.get_connection().await?) - .await - .optional() - .map_err(DatabaseError::backend)?; - Ok(res) - } -} - -impl NostrEventsDatabase for NostrPostgres { - /// Save [`Event`] into store - /// - /// **This method assumes that [`Event`] was already verified** - fn save_event<'a>( - &'a self, - event: &'a Event, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { self.save(EventDataDb::try_from(event)?).await }) - } - - /// Check event status by ID - /// - /// Check if the event is saved, deleted or not existent. - fn check_id<'a>( - &'a self, - event_id: &'a EventId, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let status = match self.event_by_id(event_id).await? { - Some(e) if e.deleted => DatabaseEventStatus::Deleted, - Some(_) => DatabaseEventStatus::Saved, - None => DatabaseEventStatus::NotExistent, - }; - Ok(status) - }) - } - - /// Coordinate feature is not supported yet - fn has_coordinate_been_deleted<'a>( - &'a self, - _coordinate: &'a nostr::nips::nip01::CoordinateBorrow<'a>, - _timestamp: &'a Timestamp, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { Ok(false) }) - } - - /// Get [`Event`] by [`EventId`] - fn event_by_id<'a>( - &'a self, - event_id: &'a EventId, - ) -> BoxedFuture<'a, Result, DatabaseError>> { - Box::pin(async move { - let event = match self.event_by_id(event_id).await? { - Some(e) if !e.deleted => { - Some(Event::decode(&e.payload).map_err(DatabaseError::backend)?) - } - _ => None, - }; - Ok(event) - }) - } - - /// Count the number of events found with [`Filter`]. - /// - /// Use `Filter::new()` or `Filter::default()` to count all events. - fn count(&self, filter: Filter) -> BoxedFuture> { - Box::pin(async move { - let res: i64 = build_filter_query(filter) - .count() - .get_result(&mut self.get_connection().await?) - .await - .map_err(DatabaseError::backend)?; - Ok(res as usize) - }) - } - - /// Query stored events. - fn query(&self, filter: Filter) -> BoxedFuture> { - let filter = with_limit(filter, 10000); - Box::pin(async move { - let mut events = Events::new(&filter); - let result = build_filter_query(filter.clone()) - .select(EventDb::as_select()) - .load(&mut self.get_connection().await?) - .await - .map_err(DatabaseError::backend)?; - - for item in result.into_iter() { - if let Ok(event) = Event::decode(&item.payload) { - events.insert(event); - } - } - Ok(events) - }) - } - - /// Delete all events that match the [Filter] - fn delete(&self, filter: Filter) -> BoxedFuture> { - let filter = with_limit(filter, 999); - Box::pin(async move { - let filter = build_filter_query(filter); - diesel::update(events::table) - .set(events::deleted.eq(true)) - .filter(events::id.eq_any(filter.select(events::id))) - .execute(&mut self.get_connection().await?) - .await - .map_err(DatabaseError::backend)?; - - Ok(()) - }) - } -} - -impl NostrDatabase for NostrPostgres { - fn backend(&self) -> Backend { - Backend::Custom("Postgres".to_string()) - } -} - -/// Create a new [`NostrPostgres`] instance from an existing connection pool -impl From for NostrPostgres { - fn from(pool: PostgresConnectionPool) -> Self { - Self { pool } - } -} - -/// Create a connection pool for a Postgres database with the given connection string. -pub async fn postgres_connection_pool( - connection_string: C, -) -> Result -where - C: AsRef, -{ - let config = AsyncDieselConnectionManager::::new(connection_string.as_ref()); - let pool: PostgresConnectionPool = Pool::builder(config) - .build() - .map_err(|e| DatabaseError::Backend(Box::new(e)))?; - Ok(pool) -} - -impl std::fmt::Debug for NostrPostgres { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NostrPostgres") - .field("pool", &self.pool.status()) - .finish() - } -} - -/// For now we want to avoid wiping the database -impl NostrDatabaseWipe for NostrPostgres { - #[inline] - fn wipe(&self) -> BoxedFuture> { - Box::pin(async move { Err(DatabaseError::NotSupported) }) - } -} diff --git a/database/nostr-sqldb/src/query.rs b/database/nostr-sqldb/src/query.rs deleted file mode 100644 index 272536b3d..000000000 --- a/database/nostr-sqldb/src/query.rs +++ /dev/null @@ -1,126 +0,0 @@ -use diesel::dsl::{AsSelect, Eq, Filter as DieselFilter, InnerJoin, IntoBoxed, SqlTypeOf}; -use diesel::expression::SqlLiteral; -use diesel::prelude::*; -use diesel::sql_types::Binary; -use nostr::event::*; -use nostr::filter::Filter; -use nostr_database::*; - -use super::model::EventDb; -#[cfg(feature = "mysql")] -use super::schema::mysql::{event_tags, events}; -#[cfg(feature = "postgres")] -use super::schema::postgres::{event_tags, events}; -#[cfg(feature = "sqlite")] -use super::schema::sqlite::{event_tags, events}; - -// filter type of a join query. -type QuerySetJoinTypeDb<'a, DB> = IntoBoxed< - 'a, - DieselFilter< - InnerJoin, - Eq>, - >, - DB, ->; -type SelectEventTypeDb = SqlTypeOf>; -type BoxedEventQueryDb<'a, DB> = events::BoxedQuery<'a, DB, SelectEventTypeDb>; - -#[cfg(feature = "postgres")] -type QuerySetJoinType<'a> = QuerySetJoinTypeDb<'a, diesel::pg::Pg>; -#[cfg(feature = "postgres")] -type BoxedEventQuery<'a> = BoxedEventQueryDb<'a, diesel::pg::Pg>; -#[cfg(feature = "sqlite")] -type QuerySetJoinType<'a> = QuerySetJoinTypeDb<'a, diesel::sqlite::Sqlite>; -#[cfg(feature = "sqlite")] -type BoxedEventQuery<'a> = BoxedEventQueryDb<'a, diesel::sqlite::Sqlite>; -#[cfg(feature = "mysql")] -type QuerySetJoinType<'a> = QuerySetJoinTypeDb<'a, diesel::mysql::Mysql>; -#[cfg(feature = "mysql")] -type BoxedEventQuery<'a> = BoxedEventQueryDb<'a, diesel::mysql::Mysql>; - -pub fn build_filter_query<'a>(filter: Filter) -> QuerySetJoinType<'a> { - let mut query = events::table - .distinct() - .inner_join(event_tags::table) - .filter(events::deleted.eq(false)) - .order_by(events::created_at.desc()) - .into_boxed(); - - if let Some(limit) = filter.limit { - query = query.limit(limit as i64); - } - - if !has_filters(&filter) { - return query; - } - - if let Some(ids) = filter.ids.clone() { - let values = ids - .iter() - .map(|id| id.as_bytes().to_vec()) - .collect::>(); - query = query.filter(events::id.eq_any(values)); - } - - if let Some(authors) = filter.authors.clone() { - let values = authors - .iter() - .map(|a| a.as_bytes().to_vec()) - .collect::>(); - query = query.filter(events::pubkey.eq_any(values)); - } - - if let Some(kinds) = filter.kinds.clone() { - let values = kinds.iter().map(|k| k.as_u16() as i64).collect::>(); - query = query.filter(events::kind.eq_any(values)); - } - - if let Some(since) = filter.since { - query = query.filter(events::created_at.ge(since.as_u64() as i64)); - } - - if let Some(until) = filter.until { - query = query.filter(events::created_at.le(until.as_u64() as i64)); - } - - if !filter.generic_tags.is_empty() { - for (tag, values) in filter.generic_tags.into_iter() { - let values = values.iter().map(|v| v.to_string()).collect::>(); - query = query.filter( - event_tags::tag - .eq(tag.to_string()) - .and(event_tags::tag_value.eq_any(values)), - ); - } - } - - query -} - -/// sets the given default limit on a Nostr filter if not set -pub fn with_limit(filter: Filter, default_limit: usize) -> Filter { - if filter.limit.is_none() { - return filter.limit(default_limit); - } - filter -} - -pub fn event_by_id<'a>(event_id: &EventId) -> BoxedEventQuery<'a> { - let event_id = event_id.as_bytes().to_vec(); - events::table - .select(EventDb::as_select()) - .filter(events::id.eq(event_id)) - .into_boxed() -} - -// determine if the filter has any filters set -fn has_filters(filter: &Filter) -> bool { - filter.ids.is_some() - || filter.authors.is_some() - || filter.kinds.is_some() - || filter.since.is_some() - || filter.until.is_some() - || !filter.generic_tags.is_empty() - || filter.limit.is_some() -} diff --git a/database/nostr-sqldb/src/schema/mod.rs b/database/nostr-sqldb/src/schema/mod.rs deleted file mode 100644 index 13f8538b0..000000000 --- a/database/nostr-sqldb/src/schema/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[cfg(feature = "postgres")] -pub mod postgres; - -#[cfg(feature = "mysql")] -pub mod mysql; - -#[cfg(feature = "sqlite")] -pub mod sqlite; diff --git a/database/nostr-sqldb/src/schema/mysql.rs b/database/nostr-sqldb/src/schema/mysql.rs deleted file mode 100644 index e37f29370..000000000 --- a/database/nostr-sqldb/src/schema/mysql.rs +++ /dev/null @@ -1,29 +0,0 @@ -// @generated automatically by Diesel CLI. - -diesel::table! { - event_tags (tag, tag_value, event_id) { - #[max_length = 64] - tag -> Varchar, - #[max_length = 512] - tag_value -> Varchar, - #[max_length = 32] - event_id -> Blob, - } -} - -diesel::table! { - events (id) { - #[max_length = 32] - id -> Blob, - #[max_length = 32] - pubkey -> Blob, - created_at -> Bigint, - kind -> Bigint, - payload -> Blob, - deleted -> Bool, - } -} - -diesel::joinable!(event_tags -> events (event_id)); - -diesel::allow_tables_to_appear_in_same_query!(event_tags, events); diff --git a/database/nostr-sqldb/src/schema/postgres.rs b/database/nostr-sqldb/src/schema/postgres.rs deleted file mode 100644 index 629ffcf19..000000000 --- a/database/nostr-sqldb/src/schema/postgres.rs +++ /dev/null @@ -1,24 +0,0 @@ -// @generated automatically by Diesel CLI. - -diesel::table! { - event_tags (tag, tag_value, event_id) { - tag -> Text, - tag_value -> Text, - event_id -> Bytea, - } -} - -diesel::table! { - events (id) { - id -> Bytea, - pubkey -> Bytea, - created_at -> Int8, - kind -> Int8, - payload -> Bytea, - deleted -> Bool, - } -} - -diesel::joinable!(event_tags -> events (event_id)); - -diesel::allow_tables_to_appear_in_same_query!(event_tags, events); diff --git a/database/nostr-sqldb/src/schema/sqlite.rs b/database/nostr-sqldb/src/schema/sqlite.rs deleted file mode 100644 index 0a7973a3a..000000000 --- a/database/nostr-sqldb/src/schema/sqlite.rs +++ /dev/null @@ -1,24 +0,0 @@ -// @generated automatically by Diesel CLI. - -diesel::table! { - event_tags (tag, tag_value, event_id) { - tag -> Text, - tag_value -> Text, - event_id -> Binary, - } -} - -diesel::table! { - events (id) { - id -> Binary, - pubkey -> Binary, - created_at -> BigInt, - kind -> BigInt, - payload -> Binary, - deleted -> Bool, - } -} - -diesel::joinable!(event_tags -> events (event_id)); - -diesel::allow_tables_to_appear_in_same_query!(event_tags, events); From b0f9fe82666e0f635112ece5f194e1ef6ec4b461 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 9 Jul 2025 08:01:43 +0200 Subject: [PATCH 6/8] sqldb: move from AnyPool to specific pools Signed-off-by: Yuki Kishimoto --- database/nostr-sqldb/Cargo.toml | 2 +- database/nostr-sqldb/src/db.rs | 65 +++++++++++++++++---------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/database/nostr-sqldb/Cargo.toml b/database/nostr-sqldb/Cargo.toml index 95c9787b7..355c398ea 100644 --- a/database/nostr-sqldb/Cargo.toml +++ b/database/nostr-sqldb/Cargo.toml @@ -27,7 +27,7 @@ mysql = ["sqlx/mysql", "sqlx/tls-rustls-ring-webpki"] [dependencies] nostr = { workspace = true, features = ["std"] } nostr-database = { workspace = true, features = ["flatbuf"] } -sqlx = { version = "0.8", features = ["any", "migrate", "runtime-tokio"] } +sqlx = { version = "0.8", features = ["migrate", "runtime-tokio"] } tokio = { workspace = true, features = ["sync"] } [dev-dependencies] diff --git a/database/nostr-sqldb/src/db.rs b/database/nostr-sqldb/src/db.rs index feb65b1c1..bdefad21e 100644 --- a/database/nostr-sqldb/src/db.rs +++ b/database/nostr-sqldb/src/db.rs @@ -10,9 +10,12 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; #[cfg(feature = "sqlite")] -use sqlx::sqlite::Sqlite; -use sqlx::{AnyPool, Any, Transaction, QueryBuilder}; -use sqlx::migrate::MigrateDatabase; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePool}; +#[cfg(feature = "postgres")] +use sqlx::postgres::{PgConnectOptions, PgPool}; +#[cfg(feature = "mysql")] +use sqlx::mysql::{MySql, MySqlPool}; +use sqlx::{Transaction, QueryBuilder}; use nostr_database::prelude::*; use tokio::sync::Mutex; @@ -66,21 +69,20 @@ impl NostrSqlBackend { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum PoolKind { +#[derive(Debug, Clone)] +enum Db { #[cfg(feature = "sqlite")] - Sqlite, + Sqlite(SqlitePool), #[cfg(feature = "postgres")] - Postgres, + Postgres(PgPool), #[cfg(feature = "mysql")] - MySql, + MySql(MySqlPool), } /// Nostr SQL database #[derive(Clone)] pub struct NostrSql { - pool: AnyPool, - kind: PoolKind, + pool: Db, fbb: Arc>>, } @@ -88,7 +90,6 @@ impl fmt::Debug for NostrSql { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("NostrSql") .field("pool", &self.pool) - .field("kind", &self.kind) .finish() } } @@ -96,45 +97,45 @@ impl fmt::Debug for NostrSql { impl NostrSql { /// Connect to a SQL database pub async fn new(backend: NostrSqlBackend) -> Result { - // Install drivers - sqlx::any::install_default_drivers(); - - let (pool, kind) = match backend { + let pool = match backend { #[cfg(feature = "sqlite")] NostrSqlBackend::Sqlite {path} => { - let uri: Cow = match path { - Some(path) => Cow::Owned(format!("sqlite://{}", path.display())), - None => Cow::Borrowed("sqlite:memory:"), + let mut opts: SqliteConnectOptions = SqliteConnectOptions::new().create_if_missing(true); + + match path { + Some(path) => opts = opts.filename(path), + None => opts = opts.in_memory(true), }; - - if !Sqlite::database_exists(&uri).await? { - Sqlite::create_database(&uri).await?; - } - - let pool: AnyPool = AnyPool::connect(&uri).await?; + + + let pool: SqlitePool = SqlitePool::connect_with(opts).await?; sqlx::migrate!("migrations/sqlite").run(&pool).await?; - (pool, PoolKind::Sqlite) + Db::Sqlite(pool) } #[cfg(feature = "postgres")] NostrSqlBackend::Postgres {host, port, username, password, database } => { - let uri: String = match (username, password) { - (Some(username), Some(password)) => format!("postgres://{username}:{password}@{host}:{port}/{database}"), - _ => format!("postgres://{host}:{port}/{database}") - }; + let mut opts: PgConnectOptions = PgConnectOptions::new_without_pgpass().host(&host).port(port).database(&database); + + if let Some(username) = username { + opts = opts.username(&username); + } + + if let Some(password) = password { + opts = opts.password(&password); + } - let pool: AnyPool = AnyPool::connect(&uri).await?; + let pool: PgPool = PgPool::connect_with(opts).await?; sqlx::migrate!("migrations/postgres").run(&pool).await?; - (pool, PoolKind::Postgres) + Db::Postgres(pool) } }; Ok(Self { pool, - kind, fbb: Arc::new(Mutex::new(FlatBufferBuilder::new())), }) } From 8de58f67e567d6d3a08cc8974380ecd6eca4b80b Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 12 Nov 2025 10:04:19 +0100 Subject: [PATCH 7/8] fmt Signed-off-by: Yuki Kishimoto --- crates/nostr-sdk/examples/client.rs | 5 +- database/nostr-sqldb/build.rs | 2 +- .../nostr-sqldb/examples/postgres-relay.rs | 3 +- database/nostr-sqldb/examples/sqlite-relay.rs | 1 - database/nostr-sqldb/src/db.rs | 111 +++++++++++------- database/nostr-sqldb/src/error.rs | 4 +- database/nostr-sqldb/src/lib.rs | 6 +- database/nostr-sqldb/src/model.rs | 2 +- 8 files changed, 82 insertions(+), 52 deletions(-) diff --git a/crates/nostr-sdk/examples/client.rs b/crates/nostr-sdk/examples/client.rs index be1522539..1add5a827 100644 --- a/crates/nostr-sdk/examples/client.rs +++ b/crates/nostr-sdk/examples/client.rs @@ -3,6 +3,7 @@ // Distributed under the MIT software license use std::time::Duration; + use nostr_sdk::prelude::*; #[tokio::main] @@ -22,7 +23,7 @@ async fn main() -> Result<()> { println!("Event ID: {}", output.id().to_bech32()?); println!("Sent to: {:?}", output.success); println!("Not sent to: {:?}", output.failed); - + let events = client .fetch_events(Filter::new().kind(Kind::TextNote), Duration::from_secs(10)) .await?; @@ -30,6 +31,6 @@ async fn main() -> Result<()> { for event in events { println!("{}", event.as_json()) } - + Ok(()) } diff --git a/database/nostr-sqldb/build.rs b/database/nostr-sqldb/build.rs index d278795c9..4ca5b40bf 100644 --- a/database/nostr-sqldb/build.rs +++ b/database/nostr-sqldb/build.rs @@ -4,4 +4,4 @@ fn main() { println!("cargo:rerun-if-changed=migrations"); -} \ No newline at end of file +} diff --git a/database/nostr-sqldb/examples/postgres-relay.rs b/database/nostr-sqldb/examples/postgres-relay.rs index b14155fb6..0ad3f94c6 100644 --- a/database/nostr-sqldb/examples/postgres-relay.rs +++ b/database/nostr-sqldb/examples/postgres-relay.rs @@ -2,7 +2,6 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license - use std::time::Duration; use nostr_database::prelude::*; @@ -20,7 +19,7 @@ async fn main() -> Result<()> { password: Some(String::from("password")), database: String::from("nostr"), }; - + // Create a nostr db instance and run pending db migrations if any let db = NostrSql::new(backend).await?; diff --git a/database/nostr-sqldb/examples/sqlite-relay.rs b/database/nostr-sqldb/examples/sqlite-relay.rs index 4e1e3f27e..3c95d5b66 100644 --- a/database/nostr-sqldb/examples/sqlite-relay.rs +++ b/database/nostr-sqldb/examples/sqlite-relay.rs @@ -2,7 +2,6 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license - use std::time::Duration; use nostr_database::prelude::*; diff --git a/database/nostr-sqldb/src/db.rs b/database/nostr-sqldb/src/db.rs index bdefad21e..b8e5a29be 100644 --- a/database/nostr-sqldb/src/db.rs +++ b/database/nostr-sqldb/src/db.rs @@ -9,14 +9,14 @@ use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; -#[cfg(feature = "sqlite")] -use sqlx::sqlite::{SqliteConnectOptions, SqlitePool}; -#[cfg(feature = "postgres")] -use sqlx::postgres::{PgConnectOptions, PgPool}; +use nostr_database::prelude::*; #[cfg(feature = "mysql")] use sqlx::mysql::{MySql, MySqlPool}; -use sqlx::{Transaction, QueryBuilder}; -use nostr_database::prelude::*; +#[cfg(feature = "postgres")] +use sqlx::postgres::{PgConnectOptions, PgPool}; +#[cfg(feature = "sqlite")] +use sqlx::sqlite::{SqliteConnectOptions, SqlitePool}; +use sqlx::{QueryBuilder, Transaction}; use tokio::sync::Mutex; use crate::error::Error; @@ -32,7 +32,7 @@ pub enum NostrSqlBackend { /// SQLite database path /// /// If no path is passed, an in-memory database will be created. - path: Option + path: Option, }, /// Postgres #[cfg(feature = "postgres")] @@ -47,7 +47,7 @@ pub enum NostrSqlBackend { password: Option, /// Database name database: String, - } + }, } impl NostrSqlBackend { @@ -56,9 +56,11 @@ impl NostrSqlBackend { #[cfg(feature = "sqlite")] pub fn sqlite

(path: P) -> Self where - P: AsRef + P: AsRef, { - Self::Sqlite { path: Some(path.as_ref().to_path_buf()) } + Self::Sqlite { + path: Some(path.as_ref().to_path_buf()), + } } /// New in-memory SQLite database @@ -99,15 +101,15 @@ impl NostrSql { pub async fn new(backend: NostrSqlBackend) -> Result { let pool = match backend { #[cfg(feature = "sqlite")] - NostrSqlBackend::Sqlite {path} => { - let mut opts: SqliteConnectOptions = SqliteConnectOptions::new().create_if_missing(true); - + NostrSqlBackend::Sqlite { path } => { + let mut opts: SqliteConnectOptions = + SqliteConnectOptions::new().create_if_missing(true); + match path { Some(path) => opts = opts.filename(path), None => opts = opts.in_memory(true), }; - - + let pool: SqlitePool = SqlitePool::connect_with(opts).await?; sqlx::migrate!("migrations/sqlite").run(&pool).await?; @@ -115,9 +117,18 @@ impl NostrSql { Db::Sqlite(pool) } #[cfg(feature = "postgres")] - NostrSqlBackend::Postgres {host, port, username, password, database } => { - let mut opts: PgConnectOptions = PgConnectOptions::new_without_pgpass().host(&host).port(port).database(&database); - + NostrSqlBackend::Postgres { + host, + port, + username, + password, + database, + } => { + let mut opts: PgConnectOptions = PgConnectOptions::new_without_pgpass() + .host(&host) + .port(port) + .database(&database); + if let Some(username) = username { opts = opts.username(&username); } @@ -141,7 +152,11 @@ impl NostrSql { } /// Returns true if successfully inserted - async fn insert_event_tx(&self, tx: &mut Transaction<'_, Any>, event: &EventDb) -> Result { + async fn insert_event_tx( + &self, + tx: &mut Transaction<'_, Any>, + event: &EventDb, + ) -> Result { let sql: &str = match self.kind { #[cfg(feature = "sqlite")] PoolKind::Sqlite => { @@ -170,7 +185,11 @@ impl NostrSql { Ok(result.rows_affected() > 0) } - async fn insert_tags_tx(&self, tx: &mut Transaction<'_, Any>, tags: &[EventTagDb]) -> Result<(), Error> { + async fn insert_tags_tx( + &self, + tx: &mut Transaction<'_, Any>, + tags: &[EventTagDb], + ) -> Result<(), Error> { let sql: &str = match self.kind { #[cfg(feature = "sqlite")] PoolKind::Sqlite => { @@ -198,7 +217,6 @@ impl NostrSql { Ok(()) } - async fn _save_event(&self, event: &Event) -> Result { if event.kind.is_ephemeral() { return Ok(SaveEventStatus::Rejected(RejectedReason::Ephemeral)); @@ -260,7 +278,9 @@ impl NostrDatabase for NostrSql { event: &'a Event, ) -> BoxedFuture<'a, Result> { Box::pin(async move { - self._save_event(event).await.map_err(DatabaseError::backend) + self._save_event(event) + .await + .map_err(DatabaseError::backend) }) } @@ -269,7 +289,11 @@ impl NostrDatabase for NostrSql { event_id: &'a EventId, ) -> BoxedFuture<'a, Result> { Box::pin(async move { - match self.get_event_by_id(event_id).await.map_err(DatabaseError::backend)? { + match self + .get_event_by_id(event_id) + .await + .map_err(DatabaseError::backend)? + { Some(e) if e.deleted => Ok(DatabaseEventStatus::Deleted), Some(_) => Ok(DatabaseEventStatus::Saved), None => Ok(DatabaseEventStatus::NotExistent), @@ -282,19 +306,21 @@ impl NostrDatabase for NostrSql { event_id: &'a EventId, ) -> BoxedFuture<'a, Result, DatabaseError>> { Box::pin(async move { - match self.get_event_by_id(event_id).await.map_err(DatabaseError::backend)? { - Some(e) if !e.deleted => { - Ok(Some(Event::decode(&e.payload).map_err(DatabaseError::backend)?)) - } + match self + .get_event_by_id(event_id) + .await + .map_err(DatabaseError::backend)? + { + Some(e) if !e.deleted => Ok(Some( + Event::decode(&e.payload).map_err(DatabaseError::backend)?, + )), _ => Ok(None), } }) } fn count(&self, filter: Filter) -> BoxedFuture> { - Box::pin(async move { - Ok(self.query(filter).await?.len()) - }) + Box::pin(async move { Ok(self.query(filter).await?.len()) }) } fn query(&self, filter: Filter) -> BoxedFuture> { @@ -306,7 +332,10 @@ impl NostrDatabase for NostrSql { let sql = build_filter_query(filter); - let payloads: Vec<(Vec,)> = sqlx::query_as(&sql).fetch_all(&self.pool).await.map_err(DatabaseError::backend)?; + let payloads: Vec<(Vec,)> = sqlx::query_as(&sql) + .fetch_all(&self.pool) + .await + .map_err(DatabaseError::backend)?; for (payload,) in payloads.into_iter() { if let Ok(event) = Event::decode(&payload) { @@ -330,11 +359,11 @@ impl NostrDatabase for NostrSql { // // Ok(()) // }) - Box::pin(async move { Err(DatabaseError::NotSupported )}) + Box::pin(async move { Err(DatabaseError::NotSupported) }) } fn wipe(&self) -> BoxedFuture> { - Box::pin(async move { Err(DatabaseError::NotSupported )}) + Box::pin(async move { Err(DatabaseError::NotSupported) }) } } @@ -343,7 +372,7 @@ fn build_filter_query(filter: Filter) -> String { "SELECT DISTINCT e.payload FROM events e INNER JOIN event_tags et ON e.id = et.event_id - WHERE e.deleted = 0" + WHERE e.deleted = 0", ); // Add filters @@ -393,10 +422,12 @@ fn build_filter_query(filter: Filter) -> String { if !filter.generic_tags.is_empty() { for (tag, values) in filter.generic_tags { if !values.is_empty() { - query_builder.push(" AND EXISTS ( + query_builder.push( + " AND EXISTS ( SELECT 1 FROM event_tags et2 WHERE et2.event_id = e.id - AND et2.tag = "); + AND et2.tag = ", + ); query_builder.push_bind(tag.to_string()); query_builder.push(" AND et2.tag_value IN ("); @@ -492,13 +523,13 @@ mod tests { EventBuilder::metadata( &Metadata::new().name("account-a").display_name("Account A"), ) - .sign_with_keys(&keys_a) - .unwrap(), + .sign_with_keys(&keys_a) + .unwrap(), EventBuilder::metadata( &Metadata::new().name("account-b").display_name("Account B"), ) - .sign_with_keys(&keys_b) - .unwrap(), + .sign_with_keys(&keys_b) + .unwrap(), EventBuilder::new(Kind::Custom(33_333), "") .tag(Tag::identifier("my-id-a")) .sign_with_keys(&keys_a) diff --git a/database/nostr-sqldb/src/error.rs b/database/nostr-sqldb/src/error.rs index a1761bb3a..8a6f6b2f2 100644 --- a/database/nostr-sqldb/src/error.rs +++ b/database/nostr-sqldb/src/error.rs @@ -25,7 +25,7 @@ impl fmt::Display for Error { Self::Sqlx(e) => write!(f, "{e}"), Self::Migrate(e) => write!(f, "{e}"), } - } + } } impl From for Error { @@ -38,4 +38,4 @@ impl From for Error { fn from(e: MigrateError) -> Self { Self::Migrate(e) } -} \ No newline at end of file +} diff --git a/database/nostr-sqldb/src/lib.rs b/database/nostr-sqldb/src/lib.rs index ca843e645..17950aa3b 100644 --- a/database/nostr-sqldb/src/lib.rs +++ b/database/nostr-sqldb/src/lib.rs @@ -13,8 +13,8 @@ #[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))] compile_error!("At least one database backend must be enabled"); -mod model; -pub mod error; pub mod db; +pub mod error; +mod model; -pub use self::db::{NostrSql, NostrSqlBackend}; \ No newline at end of file +pub use self::db::{NostrSql, NostrSqlBackend}; diff --git a/database/nostr-sqldb/src/model.rs b/database/nostr-sqldb/src/model.rs index 46ca9e6f5..33690d423 100644 --- a/database/nostr-sqldb/src/model.rs +++ b/database/nostr-sqldb/src/model.rs @@ -2,9 +2,9 @@ // Copyright (c) 2023-2025 Rust Nostr Developers // Distributed under the MIT software license -use sqlx::FromRow; use nostr::event::Event; use nostr_database::{FlatBufferBuilder, FlatBufferEncode}; +use sqlx::FromRow; #[derive(Debug, Clone, FromRow)] pub(crate) struct EventDb { From 112d6a6fadfb2c34eb6d4c3d1ce3630b10806889 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 12 Nov 2025 13:27:59 +0100 Subject: [PATCH 8/8] WIP Signed-off-by: Yuki Kishimoto --- Cargo.lock | 128 +++--- Cargo.toml | 1 + database/nostr-sqldb/Cargo.toml | 3 +- database/nostr-sqldb/examples/sqlite-relay.rs | 5 +- database/nostr-sqldb/src/db.rs | 372 +++--------------- database/nostr-sqldb/src/lib.rs | 2 + database/nostr-sqldb/src/model.rs | 9 +- database/nostr-sqldb/src/sqlite.rs | 109 +++++ 8 files changed, 238 insertions(+), 391 deletions(-) create mode 100644 database/nostr-sqldb/src/sqlite.rs diff --git a/Cargo.lock b/Cargo.lock index 0fc33bb3f..e484d6487 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,7 +215,7 @@ dependencies = [ "rand", "safelog", "serde", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-async-utils", "tor-basic-utils", "tor-chanmgr", @@ -259,7 +259,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.8", + "thiserror 2.0.17", ] [[package]] @@ -1155,7 +1155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b84d32b18d9a256d81e4fec2e4cfd0ab6dde5e5ff49be1713ae0adbd0060c2" dependencies = [ "heck 0.5.0", - "indexmap 1.9.3", + "indexmap 2.12.0", "itertools 0.12.1", "proc-macro-crate", "proc-macro2", @@ -1173,7 +1173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b48e8e38a4aa565da767322b5ca55fb0f8347983c5bc7f7647db069405420479" dependencies = [ "heck 0.5.0", - "indexmap 1.9.3", + "indexmap 2.12.0", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", @@ -1675,7 +1675,7 @@ dependencies = [ "once_cell", "pwd-grp", "serde", - "thiserror 2.0.8", + "thiserror 2.0.17", "walkdir", ] @@ -1706,7 +1706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f261f25f1e94963fe8f72863f4da841b280fa3b5a573990b425a26b585a54578" dependencies = [ "fslock-arti-fork", - "thiserror 2.0.8", + "thiserror 2.0.17", "winapi", ] @@ -2996,10 +2996,11 @@ dependencies = [ [[package]] name = "nostr-sqldb" -version = "0.42.0" +version = "0.44.0" dependencies = [ "nostr", "nostr-database", + "nostr-database-test-suite", "nostr-relay-builder", "sqlx", "tempfile", @@ -3018,7 +3019,7 @@ dependencies = [ "flatbuffers 23.5.26", "futures", "libc", - "thiserror 2.0.8", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -3730,7 +3731,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 2.0.8", + "thiserror 2.0.17", ] [[package]] @@ -3966,9 +3967,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "log", "once_cell", @@ -3991,15 +3992,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring 0.17.14", "rustls-pki-types", @@ -4028,7 +4032,7 @@ dependencies = [ "educe", "either", "fluid-let", - "thiserror 2.0.8", + "thiserror 2.0.17", ] [[package]] @@ -4390,7 +4394,7 @@ dependencies = [ "paste", "serde", "slotmap", - "thiserror 2.0.8", + "thiserror 2.0.17", "void", ] @@ -4479,7 +4483,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.8", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -4562,7 +4566,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.8", + "thiserror 2.0.17", "tracing", "whoami", ] @@ -4599,7 +4603,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.8", + "thiserror 2.0.17", "tracing", "whoami", ] @@ -4623,7 +4627,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.8", + "thiserror 2.0.17", "tracing", "url", ] @@ -4816,11 +4820,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.8" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.8", + "thiserror-impl 2.0.17", ] [[package]] @@ -4836,9 +4840,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.8" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -5049,7 +5053,7 @@ dependencies = [ "oneshot-fused-workaround", "pin-project", "postage", - "thiserror 2.0.8", + "thiserror 2.0.17", "void", ] @@ -5069,7 +5073,7 @@ dependencies = [ "serde", "slab", "smallvec", - "thiserror 2.0.8", + "thiserror 2.0.17", ] [[package]] @@ -5084,7 +5088,7 @@ dependencies = [ "educe", "getrandom 0.2.15", "safelog", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-error", "tor-llcrypto", "zeroize", @@ -5106,7 +5110,7 @@ dependencies = [ "paste", "rand", "smallvec", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-basic-utils", "tor-bytes", "tor-cert", @@ -5129,7 +5133,7 @@ dependencies = [ "derive_builder_fork_arti", "derive_more 2.0.1", "digest", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-bytes", "tor-checkable", "tor-llcrypto", @@ -5152,7 +5156,7 @@ dependencies = [ "rand", "safelog", "serde", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-async-utils", "tor-basic-utils", "tor-cell", @@ -5178,7 +5182,7 @@ checksum = "8f1671c146d35ead4a350a50d7d2b25230635c0271539d310d92ea8d7c777313" dependencies = [ "humantime", "signature", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-llcrypto", ] @@ -5208,7 +5212,7 @@ dependencies = [ "safelog", "serde", "static_assertions", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-async-utils", "tor-basic-utils", "tor-chanmgr", @@ -5255,7 +5259,7 @@ dependencies = [ "serde-value", "serde_ignored", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "toml", "tor-basic-utils", "tor-error", @@ -5274,7 +5278,7 @@ dependencies = [ "once_cell", "serde", "shellexpand", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-error", "tor-general-addr", ] @@ -5287,7 +5291,7 @@ checksum = "9b9c48e1e8cc9c925ae5bdca8c71952886d2407f1f286cc4d8f4f7aad082d6a6" dependencies = [ "digest", "hex", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-llcrypto", ] @@ -5307,7 +5311,7 @@ dependencies = [ "httpdate", "itertools 0.14.0", "memchr", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-circmgr", "tor-error", "tor-hscrypto", @@ -5351,7 +5355,7 @@ dependencies = [ "serde", "signature", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "time", "tor-async-utils", "tor-basic-utils", @@ -5384,7 +5388,7 @@ dependencies = [ "retry-error", "static_assertions", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "tracing", "void", ] @@ -5396,7 +5400,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f35f8ecb457f99f655c805f6c5cc855c63e71fa84c24a48e11e9fc51a7d7ad4b" dependencies = [ "derive_more 2.0.1", - "thiserror 2.0.8", + "thiserror 2.0.17", "void", ] @@ -5425,7 +5429,7 @@ dependencies = [ "safelog", "serde", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-async-utils", "tor-basic-utils", "tor-config", @@ -5462,7 +5466,7 @@ dependencies = [ "safelog", "slotmap-careful", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-async-utils", "tor-basic-utils", "tor-bytes", @@ -5502,7 +5506,7 @@ dependencies = [ "safelog", "signature", "subtle", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-basic-utils", "tor-bytes", "tor-error", @@ -5528,7 +5532,7 @@ dependencies = [ "safelog", "serde", "serde_with", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-async-utils", "tor-cell", "tor-config", @@ -5573,7 +5577,7 @@ dependencies = [ "serde", "serde_with", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-async-utils", "tor-basic-utils", "tor-bytes", @@ -5612,7 +5616,7 @@ dependencies = [ "rand", "signature", "ssh-key", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-bytes", "tor-cert", "tor-checkable", @@ -5643,7 +5647,7 @@ dependencies = [ "serde", "signature", "ssh-key", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-basic-utils", "tor-bytes", "tor-config", @@ -5676,7 +5680,7 @@ dependencies = [ "serde", "serde_with", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-basic-utils", "tor-bytes", "tor-config", @@ -5712,7 +5716,7 @@ dependencies = [ "sha3", "signature", "subtle", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-memquota", "visibility", "x25519-dalek", @@ -5728,7 +5732,7 @@ dependencies = [ "futures", "humantime", "once_cell", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-error", "tor-rtcompat", "tracing", @@ -5752,7 +5756,7 @@ dependencies = [ "serde", "slotmap-careful", "static_assertions", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-async-utils", "tor-basic-utils", "tor-config", @@ -5782,7 +5786,7 @@ dependencies = [ "serde", "static_assertions", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "time", "tor-basic-utils", "tor-error", @@ -5821,7 +5825,7 @@ dependencies = [ "signature", "smallvec", "subtle", - "thiserror 2.0.8", + "thiserror 2.0.17", "time", "tinystr", "tor-basic-utils", @@ -5860,7 +5864,7 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", - "thiserror 2.0.8", + "thiserror 2.0.17", "time", "tor-async-utils", "tor-basic-utils", @@ -5899,7 +5903,7 @@ dependencies = [ "slotmap-careful", "static_assertions", "subtle", - "thiserror 2.0.8", + "thiserror 2.0.17", "tokio", "tokio-util", "tor-async-utils", @@ -5932,7 +5936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a7d228eda4c7e7c96fff6a5f6759d1bd03bad69b62b9d94f2ac409de3518b8a" dependencies = [ "caret", - "thiserror 2.0.8", + "thiserror 2.0.17", ] [[package]] @@ -5968,7 +5972,7 @@ dependencies = [ "paste", "pin-project", "rustls-pki-types", - "thiserror 2.0.8", + "thiserror 2.0.17", "tokio", "tokio-util", "tor-error", @@ -5997,7 +6001,7 @@ dependencies = [ "priority-queue", "slotmap-careful", "strum", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-error", "tor-general-addr", "tor-rtcompat", @@ -6018,7 +6022,7 @@ dependencies = [ "educe", "safelog", "subtle", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-bytes", "tor-error", ] @@ -6031,7 +6035,7 @@ checksum = "7388f506c9278d07421e6799aa8a912adee4ea6921b3dd08a1247a619de82124" dependencies = [ "derive-deftly 1.0.1", "derive_more 2.0.1", - "thiserror 2.0.8", + "thiserror 2.0.17", "tor-memquota", ] @@ -6146,7 +6150,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.8", + "thiserror 2.0.17", "utf-8", ] diff --git a/Cargo.toml b/Cargo.toml index 495fc59f9..b5ae16fc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ nostr-gossip-memory = { version = "0.44", path = "./gossip/nostr-gossip-memory", nostr-gossip-test-suite = { path = "./gossip/nostr-gossip-test-suite" } nostr-lmdb = { version = "0.44", path = "./database/nostr-lmdb", default-features = false } nostr-ndb = { version = "0.44", path = "./database/nostr-ndb", default-features = false } +nostr-sqldb = { version = "0.44", path = "./database/nostr-sqldb", default-features = false } nostr-relay-builder = { version = "0.44", path = "./crates/nostr-relay-builder", default-features = false } nostr-relay-pool = { version = "0.44", path = "./crates/nostr-relay-pool", default-features = false } reqwest = { version = "0.12", default-features = false } diff --git a/database/nostr-sqldb/Cargo.toml b/database/nostr-sqldb/Cargo.toml index 355c398ea..473d9d42a 100644 --- a/database/nostr-sqldb/Cargo.toml +++ b/database/nostr-sqldb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nostr-sqldb" -version = "0.42.0" +version = "0.44.0" edition = "2021" description = "SQL storage backend for Nostr apps" authors.workspace = true @@ -31,6 +31,7 @@ sqlx = { version = "0.8", features = ["migrate", "runtime-tokio"] } tokio = { workspace = true, features = ["sync"] } [dev-dependencies] +nostr-database-test-suite.workspace = true nostr-relay-builder.workspace = true tempfile.workspace = true tokio.workspace = true diff --git a/database/nostr-sqldb/examples/sqlite-relay.rs b/database/nostr-sqldb/examples/sqlite-relay.rs index 3c95d5b66..0ad681c73 100644 --- a/database/nostr-sqldb/examples/sqlite-relay.rs +++ b/database/nostr-sqldb/examples/sqlite-relay.rs @@ -21,8 +21,9 @@ async fn main() -> Result<()> { let builder = RelayBuilder::default().database(db); // Create local relay - let relay = LocalRelay::run(builder).await?; - println!("Url: {}", relay.url()); + let relay = LocalRelay::new(builder); + relay.run().await?; + println!("Url: {}", relay.url().await); // Keep up the program loop { diff --git a/database/nostr-sqldb/src/db.rs b/database/nostr-sqldb/src/db.rs index b8e5a29be..ab3c021f2 100644 --- a/database/nostr-sqldb/src/db.rs +++ b/database/nostr-sqldb/src/db.rs @@ -4,19 +4,22 @@ //! Nostr SQL -use std::borrow::Cow; use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; use nostr_database::prelude::*; +use sqlx::migrate::Migrator; #[cfg(feature = "mysql")] -use sqlx::mysql::{MySql, MySqlPool}; +use sqlx::mysql::MySqlConnectOptions; #[cfg(feature = "postgres")] -use sqlx::postgres::{PgConnectOptions, PgPool}; +use sqlx::postgres::PgConnectOptions; #[cfg(feature = "sqlite")] -use sqlx::sqlite::{SqliteConnectOptions, SqlitePool}; -use sqlx::{QueryBuilder, Transaction}; +use sqlx::sqlite::SqliteConnectOptions; +use sqlx::{ + Any, AnyConnection, AnyPool, ConnectOptions, Database, Pool, QueryBuilder, Sqlite, Transaction, + Type, +}; use tokio::sync::Mutex; use crate::error::Error; @@ -71,20 +74,21 @@ impl NostrSqlBackend { } } -#[derive(Debug, Clone)] -enum Db { +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum PoolKind { #[cfg(feature = "sqlite")] - Sqlite(SqlitePool), + Sqlite, #[cfg(feature = "postgres")] - Postgres(PgPool), + Postgres, #[cfg(feature = "mysql")] - MySql(MySqlPool), + MySql, } /// Nostr SQL database #[derive(Clone)] pub struct NostrSql { - pool: Db, + pool: AnyPool, + kind: PoolKind, fbb: Arc>>, } @@ -99,7 +103,10 @@ impl fmt::Debug for NostrSql { impl NostrSql { /// Connect to a SQL database pub async fn new(backend: NostrSqlBackend) -> Result { - let pool = match backend { + // Install drivers + sqlx::any::install_default_drivers(); + + let (uri, kind) = match backend { #[cfg(feature = "sqlite")] NostrSqlBackend::Sqlite { path } => { let mut opts: SqliteConnectOptions = @@ -110,11 +117,7 @@ impl NostrSql { None => opts = opts.in_memory(true), }; - let pool: SqlitePool = SqlitePool::connect_with(opts).await?; - - sqlx::migrate!("migrations/sqlite").run(&pool).await?; - - Db::Sqlite(pool) + (opts.to_url_lossy(), PoolKind::Sqlite) } #[cfg(feature = "postgres")] NostrSqlBackend::Postgres { @@ -137,16 +140,25 @@ impl NostrSql { opts = opts.password(&password); } - let pool: PgPool = PgPool::connect_with(opts).await?; + (opts.to_url_lossy(), PoolKind::Postgres) + } + }; - sqlx::migrate!("migrations/postgres").run(&pool).await?; + let pool: AnyPool = AnyPool::connect(uri.as_str()).await?; - Db::Postgres(pool) - } + let migrator: Migrator = match kind { + #[cfg(feature = "sqlite")] + PoolKind::Sqlite => sqlx::migrate!("migrations/sqlite"), + #[cfg(feature = "postgres")] + PoolKind::Postgres => sqlx::migrate!("migrations/postgres"), + #[cfg(feature = "mysql")] + PoolKind::MySql => sqlx::migrate!("migrations/mysql"), }; + migrator.run(&pool).await?; Ok(Self { pool, + kind, fbb: Arc::new(Mutex::new(FlatBufferBuilder::new())), }) } @@ -255,9 +267,13 @@ impl NostrSql { } async fn get_event_by_id(&self, id: &EventId) -> Result, Error> { - let query = sqlx::query_as::("SELECT * FROM events WHERE id = ?") - .bind(id.as_bytes().to_vec()); - Ok(query.fetch_optional(&self.pool).await?) + let event: Option = sqlx::query_as( + "SELECT id, pubkey, created_at, kind, payload, deleted FROM events WHERE id = ?", + ) + .bind(id.as_bytes().to_vec()) + .fetch_optional(&self.pool) + .await?; + Ok(event) } } @@ -294,7 +310,7 @@ impl NostrDatabase for NostrSql { .await .map_err(DatabaseError::backend)? { - Some(e) if e.deleted => Ok(DatabaseEventStatus::Deleted), + Some(e) if e.is_deleted() => Ok(DatabaseEventStatus::Deleted), Some(_) => Ok(DatabaseEventStatus::Saved), None => Ok(DatabaseEventStatus::NotExistent), } @@ -311,7 +327,7 @@ impl NostrDatabase for NostrSql { .await .map_err(DatabaseError::backend)? { - Some(e) if !e.deleted => Ok(Some( + Some(e) if !e.is_deleted() => Ok(Some( Event::decode(&e.payload).map_err(DatabaseError::backend)?, )), _ => Ok(None), @@ -411,12 +427,12 @@ fn build_filter_query(filter: Filter) -> String { if let Some(since) = filter.since { query_builder.push(" AND e.created_at >= "); - query_builder.push_bind(since.as_u64() as i64); + query_builder.push_bind(since.as_secs() as i64); } if let Some(until) = filter.until { query_builder.push(" AND e.created_at <= "); - query_builder.push_bind(until.as_u64() as i64); + query_builder.push_bind(until.as_secs() as i64); } if !filter.generic_tags.is_empty() { @@ -460,30 +476,11 @@ fn with_limit(filter: Filter, default_limit: usize) -> Filter { #[cfg(test)] mod tests { - use std::ops::Deref; - use std::time::Duration; - + use nostr_database_test_suite::database_unit_tests; use tempfile::TempDir; use super::*; - const EVENTS: [&str; 14] = [ - r#"{"id":"b7b1fb52ad8461a03e949820ae29a9ea07e35bcd79c95c4b59b0254944f62805","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644581,"kind":1,"tags":[],"content":"Text note","sig":"ed73a8a4e7c26cd797a7b875c634d9ecb6958c57733305fed23b978109d0411d21b3e182cb67c8ad750884e30ca383b509382ae6187b36e76ee76e6a142c4284"}"#, - r#"{"id":"7296747d91c53f1d71778ef3e12d18b66d494a41f688ef244d518abf37c959b6","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644586,"kind":32121,"tags":[["d","id-1"]],"content":"Empty 1","sig":"8848989a8e808f7315e950f871b231c1dff7752048f8957d4a541881d2005506c30e85c7dd74dab022b3e01329c88e69c9d5d55d961759272a738d150b7dbefc"}"#, - r#"{"id":"ec6ea04ba483871062d79f78927df7979f67545b53f552e47626cb1105590442","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644591,"kind":32122,"tags":[["d","id-1"]],"content":"Empty 2","sig":"89946113a97484850fe35fefdb9120df847b305de1216dae566616fe453565e8707a4da7e68843b560fa22a932f81fc8db2b5a2acb4dcfd3caba9a91320aac92"}"#, - r#"{"id":"63b8b829aa31a2de870c3a713541658fcc0187be93af2032ec2ca039befd3f70","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644596,"kind":32122,"tags":[["d","id-2"]],"content":"","sig":"607b1a67bef57e48d17df4e145718d10b9df51831d1272c149f2ab5ad4993ae723f10a81be2403ae21b2793c8ed4c129e8b031e8b240c6c90c9e6d32f62d26ff"}"#, - r#"{"id":"6fe9119c7db13ae13e8ecfcdd2e5bf98e2940ba56a2ce0c3e8fba3d88cd8e69d","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644601,"kind":32122,"tags":[["d","id-3"]],"content":"","sig":"d07146547a726fc9b4ec8d67bbbe690347d43dadfe5d9890a428626d38c617c52e6945f2b7144c4e0c51d1e2b0be020614a5cadc9c0256b2e28069b70d9fc26e"}"#, - r#"{"id":"a82f6ebfc709f4e7c7971e6bf738e30a3bc112cfdb21336054711e6779fd49ef","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644606,"kind":32122,"tags":[["d","id-1"]],"content":"","sig":"96d3349b42ed637712b4d07f037457ab6e9180d58857df77eb5fa27ff1fd68445c72122ec53870831ada8a4d9a0b484435f80d3ff21a862238da7a723a0d073c"}"#, - r#"{"id":"8ab0cb1beceeb68f080ec11a3920b8cc491ecc7ec5250405e88691d733185832","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644611,"kind":32122,"tags":[["d","id-1"]],"content":"Test","sig":"49153b482d7110e2538eb48005f1149622247479b1c0057d902df931d5cea105869deeae908e4e3b903e3140632dc780b3f10344805eab77bb54fb79c4e4359d"}"#, - r#"{"id":"63dc49a8f3278a2de8dc0138939de56d392b8eb7a18c627e4d78789e2b0b09f2","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644616,"kind":5,"tags":[["a","32122:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:"]],"content":"","sig":"977e54e5d57d1fbb83615d3a870037d9eb5182a679ca8357523bbf032580689cf481f76c88c7027034cfaf567ba9d9fe25fc8cd334139a0117ad5cf9fe325eef"}"#, - r#"{"id":"6975ace0f3d66967f330d4758fbbf45517d41130e2639b54ca5142f37757c9eb","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644621,"kind":5,"tags":[["a","32122:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:id-2"]],"content":"","sig":"9bb09e4759899d86e447c3fa1be83905fe2eda74a5068a909965ac14fcdabaed64edaeb732154dab734ca41f2fc4d63687870e6f8e56e3d9e180e4a2dd6fb2d2"}"#, - r#"{"id":"33f5b4e6a38e107638c20f4536db35191d4b8651ba5a2cefec983b9ec2d65084","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704645586,"kind":0,"tags":[],"content":"{\"name\":\"Key A\"}","sig":"285d090f45a6adcae717b33771149f7840a8c27fb29025d63f1ab8d95614034a54e9f4f29cee9527c4c93321a7ebff287387b7a19ba8e6f764512a40e7120429"}"#, - r#"{"id":"90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704645606,"kind":0,"tags":[],"content":"{\"name\":\"key-a\",\"display_name\":\"Key A\",\"lud16\":\"keya@ln.address\"}","sig":"ec8f49d4c722b7ccae102d49befff08e62db775e5da43ef51b25c47dfdd6a09dc7519310a3a63cbdb6ec6b3250e6f19518eb47be604edeb598d16cdc071d3dbc"}"#, - r#"{"id":"a295422c636d3532875b75739e8dae3cdb4dd2679c6e4994c9a39c7ebf8bc620","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704646569,"kind":5,"tags":[["e","90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9"]],"content":"","sig":"d4dc8368a4ad27eef63cacf667345aadd9617001537497108234fc1686d546c949cbb58e007a4d4b632c65ea135af4fbd7a089cc60ab89b6901f5c3fc6a47b29"}"#, // Invalid event deletion - r#"{"id":"999e3e270100d7e1eaa98fcfab4a98274872c1f2dfdab024f32e42a5a12d5b5e","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704646606,"kind":5,"tags":[["e","90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9"]],"content":"","sig":"4f3a33fd52784cea7ca8428fd35d94d65049712e9aa11a70b1a16a1fcd761c7b7e27afac325728b1c00dfa11e33e78b2efd0430a7e4b28f4ede5b579b3f32614"}"#, - r#"{"id":"99a022e6d61c4e39c147d08a2be943b664e8030c0049325555ac1766429c2832","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1705241093,"kind":30333,"tags":[["d","multi-id"],["p","aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4"]],"content":"Multi-tags","sig":"0abfb2b696a7ed7c9e8e3bf7743686190f3f1b3d4045b72833ab6187c254f7ed278d289d52dfac3de28be861c1471421d9b1bfb5877413cbc81c84f63207a826"}"#, - ]; - struct TempDatabase { db: NostrSql, // Needed to avoid the drop and deletion of temp folder @@ -501,288 +498,13 @@ mod tests { impl TempDatabase { async fn new() -> Self { let path = tempfile::tempdir().unwrap(); - let backend = NostrSqlBackend::sqlite(path.path().join("temp.db")); + let backend = NostrSqlBackend::sqlite(path.path().join("test.db")); Self { db: NostrSql::new(backend).await.unwrap(), _temp: path, } } - - // Return the number of added events - async fn add_random_events(&self) -> usize { - let keys_a = Keys::generate(); - let keys_b = Keys::generate(); - - let events = vec![ - EventBuilder::text_note("Text Note A") - .sign_with_keys(&keys_a) - .unwrap(), - EventBuilder::text_note("Text Note B") - .sign_with_keys(&keys_b) - .unwrap(), - EventBuilder::metadata( - &Metadata::new().name("account-a").display_name("Account A"), - ) - .sign_with_keys(&keys_a) - .unwrap(), - EventBuilder::metadata( - &Metadata::new().name("account-b").display_name("Account B"), - ) - .sign_with_keys(&keys_b) - .unwrap(), - EventBuilder::new(Kind::Custom(33_333), "") - .tag(Tag::identifier("my-id-a")) - .sign_with_keys(&keys_a) - .unwrap(), - EventBuilder::new(Kind::Custom(33_333), "") - .tag(Tag::identifier("my-id-b")) - .sign_with_keys(&keys_b) - .unwrap(), - ]; - - // Store - for event in events.iter() { - self.db.save_event(event).await.unwrap(); - } - - events.len() - } - - async fn add_event(&self, builder: EventBuilder) -> (Keys, Event) { - let keys = Keys::generate(); - let event = builder.sign_with_keys(&keys).unwrap(); - self.db.save_event(&event).await.unwrap(); - (keys, event) - } - - async fn add_event_with_keys( - &self, - builder: EventBuilder, - keys: &Keys, - ) -> (Event, SaveEventStatus) { - let event = builder.sign_with_keys(keys).unwrap(); - let status = self.db.save_event(&event).await.unwrap(); - (event, status) - } - - async fn count_all(&self) -> usize { - self.db.count(Filter::new()).await.unwrap() - } } - #[tokio::test] - async fn test_event_by_id() { - let db = TempDatabase::new().await; - - let added_events: usize = db.add_random_events().await; - - let (_keys, expected_event) = db.add_event(EventBuilder::text_note("Test")).await; - - let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap(); - assert_eq!(event, expected_event); - - // Check if number of events in database match the expected - assert_eq!(db.count_all().await, added_events + 1) - } - - #[tokio::test] - async fn test_replaceable_event() { - let db = TempDatabase::new().await; - - let added_events: usize = db.add_random_events().await; - - let now = Timestamp::now(); - let metadata = Metadata::new() - .name("my-account") - .display_name("My Account"); - - let (keys, expected_event) = db - .add_event( - EventBuilder::metadata(&metadata).custom_created_at(now - Duration::from_secs(120)), - ) - .await; - - // Test event by ID - let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap(); - assert_eq!(event, expected_event); - - // Test filter query - let events = db - .query(Filter::new().author(keys.public_key).kind(Kind::Metadata)) - .await - .unwrap(); - assert_eq!(events.to_vec(), vec![expected_event.clone()]); - - // Check if number of events in database match the expected - assert_eq!(db.count_all().await, added_events + 1); - - // Replace previous event - let (new_expected_event, status) = db - .add_event_with_keys( - EventBuilder::metadata(&metadata).custom_created_at(now), - &keys, - ) - .await; - assert!(status.is_success()); - - // Test event by ID (MUST be None because replaced) - assert!(db.event_by_id(&expected_event.id).await.unwrap().is_none()); - - // Test event by ID - let event = db - .event_by_id(&new_expected_event.id) - .await - .unwrap() - .unwrap(); - assert_eq!(event, new_expected_event); - - // Test filter query - let events = db - .query(Filter::new().author(keys.public_key).kind(Kind::Metadata)) - .await - .unwrap(); - assert_eq!(events.to_vec(), vec![new_expected_event]); - - // Check if number of events in database match the expected - assert_eq!(db.count_all().await, added_events + 1); - } - - #[tokio::test] - async fn test_param_replaceable_event() { - let db = TempDatabase::new().await; - - let added_events: usize = db.add_random_events().await; - - let now = Timestamp::now(); - - let (keys, expected_event) = db - .add_event( - EventBuilder::new(Kind::Custom(33_333), "") - .tag(Tag::identifier("my-id-a")) - .custom_created_at(now - Duration::from_secs(120)), - ) - .await; - let coordinate = Coordinate::new(Kind::from(33_333), keys.public_key).identifier("my-id-a"); - - // Test event by ID - let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap(); - assert_eq!(event, expected_event); - - // Test filter query - let events = db.query(coordinate.clone().into()).await.unwrap(); - assert_eq!(events.to_vec(), vec![expected_event.clone()]); - - // Check if number of events in database match the expected - assert_eq!(db.count_all().await, added_events + 1); - - // Replace previous event - let (new_expected_event, status) = db - .add_event_with_keys( - EventBuilder::new(Kind::Custom(33_333), "Test replace") - .tag(Tag::identifier("my-id-a")) - .custom_created_at(now), - &keys, - ) - .await; - assert!(status.is_success()); - - // Test event by ID (MUST be None` because replaced) - assert!(db.event_by_id(&expected_event.id).await.unwrap().is_none()); - - // Test event by ID - let event = db - .event_by_id(&new_expected_event.id) - .await - .unwrap() - .unwrap(); - assert_eq!(event, new_expected_event); - - // Test filter query - let events = db.query(coordinate.into()).await.unwrap(); - assert_eq!(events.to_vec(), vec![new_expected_event]); - - // Check if number of events in database match the expected - assert_eq!(db.count_all().await, added_events + 1); - - // Trey to add param replaceable event with older timestamp (MUSTN'T be stored) - let (_, status) = db - .add_event_with_keys( - EventBuilder::new(Kind::Custom(33_333), "Test replace 2") - .tag(Tag::identifier("my-id-a")) - .custom_created_at(now - Duration::from_secs(2000)), - &keys, - ) - .await; - assert!(!status.is_success()); - } - - #[tokio::test] - async fn test_full_text_search() { - let db = TempDatabase::new().await; - - let _added_events: usize = db.add_random_events().await; - - let events = db.query(Filter::new().search("Account A")).await.unwrap(); - assert_eq!(events.len(), 1); - - let events = db.query(Filter::new().search("account a")).await.unwrap(); - assert_eq!(events.len(), 1); - - let events = db.query(Filter::new().search("text note")).await.unwrap(); - assert_eq!(events.len(), 2); - - let events = db.query(Filter::new().search("notes")).await.unwrap(); - assert_eq!(events.len(), 0); - - let events = db.query(Filter::new().search("hola")).await.unwrap(); - assert_eq!(events.len(), 0); - } - - #[tokio::test] - async fn test_expected_query_result() { - let db = TempDatabase::new().await; - - for event in EVENTS.into_iter() { - let event = Event::from_json(event).unwrap(); - let _ = db.save_event(&event).await; - } - - // Test expected output - let expected_output = vec![ - Event::from_json(EVENTS[13]).unwrap(), - Event::from_json(EVENTS[12]).unwrap(), - // Event 11 is invalid deletion - // Event 10 deleted by event 12 - // Event 9 replaced by event 10 - Event::from_json(EVENTS[8]).unwrap(), - // Event 7 is an invalid deletion - Event::from_json(EVENTS[6]).unwrap(), - Event::from_json(EVENTS[5]).unwrap(), - Event::from_json(EVENTS[4]).unwrap(), - // Event 3 deleted by Event 8 - // Event 2 replaced by Event 6 - Event::from_json(EVENTS[1]).unwrap(), - Event::from_json(EVENTS[0]).unwrap(), - ]; - assert_eq!( - db.query(Filter::new()).await.unwrap().to_vec(), - expected_output - ); - assert_eq!(db.count_all().await, 8); - } - - #[tokio::test] - async fn test_delete_events_with_filter() { - let db = TempDatabase::new().await; - - let added_events: usize = db.add_random_events().await; - - assert_eq!(db.count_all().await, added_events); - - // Delete all kinds except text note - let filter = Filter::new().kinds([Kind::Metadata, Kind::Custom(33_333)]); - db.delete(filter).await.unwrap(); - - assert_eq!(db.count_all().await, 2); - } + database_unit_tests!(TempDatabase, TempDatabase::new); } diff --git a/database/nostr-sqldb/src/lib.rs b/database/nostr-sqldb/src/lib.rs index 17950aa3b..0a4610689 100644 --- a/database/nostr-sqldb/src/lib.rs +++ b/database/nostr-sqldb/src/lib.rs @@ -16,5 +16,7 @@ compile_error!("At least one database backend must be enabled"); pub mod db; pub mod error; mod model; +// #[cfg(feature = "sqlite")] +// pub mod sqlite; pub use self::db::{NostrSql, NostrSqlBackend}; diff --git a/database/nostr-sqldb/src/model.rs b/database/nostr-sqldb/src/model.rs index 33690d423..7f5cbcd52 100644 --- a/database/nostr-sqldb/src/model.rs +++ b/database/nostr-sqldb/src/model.rs @@ -16,6 +16,13 @@ pub(crate) struct EventDb { pub deleted: bool, } +impl EventDb { + #[inline] + pub(super) fn is_deleted(&self) -> bool { + self.deleted + } +} + #[derive(Debug, Clone, FromRow)] pub(crate) struct EventTagDb { pub tag: String, @@ -36,7 +43,7 @@ impl EventDataDb { event: EventDb { id: event.id.as_bytes().to_vec(), pubkey: event.pubkey.as_bytes().to_vec(), - created_at: event.created_at.as_u64() as i64, + created_at: event.created_at.as_secs() as i64, kind: event.kind.as_u16() as i64, payload: event.encode(fbb).to_vec(), deleted: false, diff --git a/database/nostr-sqldb/src/sqlite.rs b/database/nostr-sqldb/src/sqlite.rs new file mode 100644 index 000000000..7dee4258e --- /dev/null +++ b/database/nostr-sqldb/src/sqlite.rs @@ -0,0 +1,109 @@ +use std::path::Path; +use std::sync::Arc; + +use sqlx::{Sqlite, SqlitePool}; +use sqlx::migrate::Migrator; +use sqlx::sqlite::SqliteConnectOptions; +use tokio::sync::Mutex; +use nostr::{Event, EventId, Filter}; +use nostr::prelude::BoxedFuture; +use nostr_database::{Backend, DatabaseError, DatabaseEventStatus, Events, FlatBufferBuilder, NostrDatabase, SaveEventStatus}; + +use crate::db::NostrSql; +use crate::error::Error; + +#[derive(Debug, Clone)] +pub struct NostrSqlite { + db: NostrSql +} + +impl NostrSqlite { + /// Open SQLite database + pub async fn open

(path: P) -> Result + where + P: AsRef, + { + // Build SQLite connection options + let opts: SqliteConnectOptions = + SqliteConnectOptions::new().create_if_missing(true).filename(path); + + // Connect to SQLite database + let pool: SqlitePool = SqlitePool::connect_with(opts).await?; + + // Run migrations + let migrator: Migrator = sqlx::migrate!("migrations/sqlite"); + migrator.run(&pool).await?; + + Ok(Self { + db: NostrSql::new(pool), + }) + } +} + +impl NostrDatabase for NostrSqlite { + fn backend(&self) -> Backend { + self.db.backend() + } + + fn save_event<'a>(&'a self, event: &'a Event) -> BoxedFuture<'a, Result> { + self.db.save_event(event) + } + + fn check_id<'a>(&'a self, event_id: &'a EventId) -> BoxedFuture<'a, Result> { + self.db.check_id(event_id) + } + + fn event_by_id<'a>(&'a self, event_id: &'a EventId) -> BoxedFuture<'a, Result, DatabaseError>> { + self.db.event_by_id(event_id) + } + + fn count(&self, filter: Filter) -> BoxedFuture> { + self.db.count(filter) + } + + fn query(&self, filter: Filter) -> BoxedFuture> { + self.db.query(filter) + } + + fn delete(&self, filter: Filter) -> BoxedFuture> { + self.db.delete(filter) + } + + fn wipe(&self) -> BoxedFuture> { + self.db.wipe() + } +} + +#[cfg(test)] +mod tests { + use nostr_database_test_suite::database_unit_tests; + use tempfile::TempDir; + + use super::*; + + struct TempDatabase { + db: NostrSqlite, + // Needed to avoid the drop and deletion of temp folder + _temp: TempDir, + } + + impl Deref for TempDatabase { + type Target = NostrSqlite; + + fn deref(&self) -> &Self::Target { + &self.db + } + } + + impl TempDatabase { + async fn new() -> Self { + let path = tempfile::tempdir().unwrap(); + Self { + db: NostrSqlite::open(path.path().join("temp.db")).await.unwrap(), + _temp: path, + } + } + } + + database_unit_tests!(TempDatabase, TempDatabase::new); +}