Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
629 changes: 475 additions & 154 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"database/nostr-indexeddb",
"database/nostr-lmdb",
"database/nostr-ndb",
"database/nostr-sqldb",

# Gossip
"gossip/nostr-gossip",
Expand Down Expand Up @@ -51,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 }
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions contrib/scripts/check-crates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -55,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
Expand Down
20 changes: 9 additions & 11 deletions crates/nostr-sdk/examples/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright (c) 2023-2025 Rust Nostr Developers
// Distributed under the MIT software license

use std::time::Duration;

use nostr_sdk::prelude::*;

#[tokio::main]
Expand All @@ -11,9 +13,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;

Expand All @@ -24,15 +24,13 @@ async fn main() -> Result<()> {
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(())
}
46 changes: 46 additions & 0 deletions database/nostr-sqldb/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[package]
name = "nostr-sqldb"
version = "0.44.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"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
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]
nostr = { workspace = true, features = ["std"] }
nostr-database = { workspace = true, features = ["flatbuf"] }
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
tracing-subscriber.workspace = true

[[example]]
name = "postgres-relay"
required-features = ["postgres"]

[[example]]
name = "sqlite-relay"
required-features = ["sqlite"]
25 changes: 25 additions & 0 deletions database/nostr-sqldb/README.md
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions database/nostr-sqldb/build.rs
Original file line number Diff line number Diff line change
@@ -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");
}
37 changes: 37 additions & 0 deletions database/nostr-sqldb/examples/postgres-relay.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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::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 = 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;
}
}
32 changes: 32 additions & 0 deletions database/nostr-sqldb/examples/sqlite-relay.rs
Original file line number Diff line number Diff line change
@@ -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::new(builder);
relay.run().await?;
println!("Url: {}", relay.url().await);

// Keep up the program
loop {
tokio::time::sleep(Duration::from_secs(60)).await;
}
}
26 changes: 26 additions & 0 deletions database/nostr-sqldb/migrations/mysql/001_init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- The actual event data
CREATE TABLE 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 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)
);
26 changes: 26 additions & 0 deletions database/nostr-sqldb/migrations/postgres/001_init.sql
Original file line number Diff line number Diff line change
@@ -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)
);
26 changes: 26 additions & 0 deletions database/nostr-sqldb/migrations/sqlite/001_init.sql
Original file line number Diff line number Diff line change
@@ -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)
);
Loading
Loading