mirror of
https://github.com/scsibug/nostr-rs-relay.git
synced 2025-07-28 07:38:27 -04:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d72af96d5f | ||
|
b4234eae25 | ||
|
d73cde2844 | ||
|
afbd7559e8 | ||
|
a6b48620fd | ||
|
d71f5cb029 | ||
|
1ed8cc08cc |
1302
Cargo.lock
generated
1302
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ mirrored on [GitHub](https://github.com/scsibug/nostr-rs-relay).
|
|||||||
|
|
||||||
[](https://builds.sr.ht/~gheartsfield/nostr-rs-relay/commits/master?)
|
[](https://builds.sr.ht/~gheartsfield/nostr-rs-relay/commits/master?)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -158,10 +158,6 @@ Proxy](docs/reverse-proxy.md).
|
|||||||
|
|
||||||
For development discussions, please feel free to use the [sourcehut
|
For development discussions, please feel free to use the [sourcehut
|
||||||
mailing list](https://lists.sr.ht/~gheartsfield/nostr-rs-relay-devel).
|
mailing list](https://lists.sr.ht/~gheartsfield/nostr-rs-relay-devel).
|
||||||
Or, drop by the [Nostr Telegram Channel](https://t.me/nostr_protocol).
|
|
||||||
|
|
||||||
To chat about `nostr-rs-relay` on `nostr` itself; visit our channel on [anigma](https://anigma.io/) or another client that supports [NIP-28](https://github.com/nostr-protocol/nips/blob/master/28.md) chats:
|
|
||||||
* `2ad246a094fee48c6e455dd13d759d5f41b5a233120f5719d81ebc1935075194`
|
|
||||||
|
|
||||||
License
|
License
|
||||||
---
|
---
|
||||||
|
@@ -15,7 +15,6 @@ use tracing::info;
|
|||||||
/// Bulk load JSONL data from STDIN to the database specified in config.toml (or ./nostr.db as a default).
|
/// Bulk load JSONL data from STDIN to the database specified in config.toml (or ./nostr.db as a default).
|
||||||
/// The database must already exist, this will not create a new one.
|
/// The database must already exist, this will not create a new one.
|
||||||
/// Tested against schema v13.
|
/// Tested against schema v13.
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
pub fn main() -> Result<()> {
|
||||||
let _trace_sub = tracing_subscriber::fmt::try_init();
|
let _trace_sub = tracing_subscriber::fmt::try_init();
|
||||||
println!("Nostr-rs-relay Bulk Loader");
|
println!("Nostr-rs-relay Bulk Loader");
|
||||||
|
18
src/db.rs
18
src/db.rs
@@ -281,15 +281,6 @@ pub async fn db_writer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send any metadata events to the NIP-05 verifier
|
|
||||||
if nip05_active && event.is_kind_metadata() {
|
|
||||||
// we are sending this prior to even deciding if we
|
|
||||||
// persist it. this allows the nip05 module to
|
|
||||||
// inspect it, update if necessary, or persist a new
|
|
||||||
// event and broadcast it itself.
|
|
||||||
metadata_tx.send(event.clone()).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
// get a validation result for use in verification and GPRC
|
// get a validation result for use in verification and GPRC
|
||||||
let validation = if nip05_active {
|
let validation = if nip05_active {
|
||||||
Some(repo.get_latest_user_verification(&event.pubkey).await)
|
Some(repo.get_latest_user_verification(&event.pubkey).await)
|
||||||
@@ -390,6 +381,15 @@ pub async fn db_writer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send any metadata events to the NIP-05 verifier
|
||||||
|
if nip05_active && event.is_kind_metadata() {
|
||||||
|
// we are sending this prior to even deciding if we
|
||||||
|
// persist it. this allows the nip05 module to
|
||||||
|
// inspect it, update if necessary, or persist a new
|
||||||
|
// event and broadcast it itself.
|
||||||
|
metadata_tx.send(event.clone()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: cache recent list of authors to remove a DB call.
|
// TODO: cache recent list of authors to remove a DB call.
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
if event.is_ephemeral() {
|
if event.is_ephemeral() {
|
||||||
|
@@ -472,12 +472,8 @@ mod tests {
|
|||||||
let mut event = Event::simple_event();
|
let mut event = Event::simple_event();
|
||||||
event.tags = vec![vec!["e".to_owned(), "foo".to_owned()]];
|
event.tags = vec![vec!["e".to_owned(), "foo".to_owned()]];
|
||||||
event.build_index();
|
event.build_index();
|
||||||
assert!(
|
assert!(event
|
||||||
event.generic_tag_val_intersect(
|
.generic_tag_val_intersect('e', &HashSet::from(["foo".to_owned(), "bar".to_owned()])));
|
||||||
'e',
|
|
||||||
&HashSet::from(["foo".to_owned(), "bar".to_owned()])
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@@ -121,7 +121,7 @@ fn body_contains_user(username: &str, address: &str, bytes: &hyper::body::Bytes)
|
|||||||
// get the pubkey for the requested user
|
// get the pubkey for the requested user
|
||||||
let check_name = names_map.get(username).and_then(serde_json::Value::as_str);
|
let check_name = names_map.get(username).and_then(serde_json::Value::as_str);
|
||||||
// ensure the address is a match
|
// ensure the address is a match
|
||||||
Ok(check_name.map_or(false, |x| x == address))
|
Ok(check_name == Some(address))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Verifier {
|
impl Verifier {
|
||||||
@@ -243,7 +243,11 @@ impl Verifier {
|
|||||||
let response_content_length = match response.body().size_hint().upper() {
|
let response_content_length = match response.body().size_hint().upper() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => {
|
||||||
info!("missing content length header for account {:?} at URL: {}", nip.to_string(), url);
|
info!(
|
||||||
|
"missing content length header for account {:?} at URL: {}",
|
||||||
|
nip.to_string(),
|
||||||
|
url
|
||||||
|
);
|
||||||
return Ok(UserWebVerificationStatus::Unknown);
|
return Ok(UserWebVerificationStatus::Unknown);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -25,7 +25,9 @@ impl EventResultStatus {
|
|||||||
pub fn to_bool(&self) -> bool {
|
pub fn to_bool(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Duplicate | Self::Saved => true,
|
Self::Duplicate | Self::Saved => true,
|
||||||
Self::Invalid | Self::Blocked | Self::RateLimited | Self::Error | Self::Restricted => false,
|
Self::Invalid | Self::Blocked | Self::RateLimited | Self::Error | Self::Restricted => {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ impl ClnRestPaymentProcessor {
|
|||||||
.ok_or(ConfigError::NotFound("rune_path".to_string()))?;
|
.ok_or(ConfigError::NotFound("rune_path".to_string()))?;
|
||||||
let rune = String::from_utf8(fs::read(rune_path)?)
|
let rune = String::from_utf8(fs::read(rune_path)?)
|
||||||
.map_err(|_| ConfigError::Message("Rune should be UTF8".to_string()))?;
|
.map_err(|_| ConfigError::Message("Rune should be UTF8".to_string()))?;
|
||||||
let mut rune_header = HeaderValue::from_str(&rune.trim())
|
let mut rune_header = HeaderValue::from_str(rune.trim())
|
||||||
.map_err(|_| ConfigError::Message("Invalid Rune header".to_string()))?;
|
.map_err(|_| ConfigError::Message("Invalid Rune header".to_string()))?;
|
||||||
rune_header.set_sensitive(true);
|
rune_header.set_sensitive(true);
|
||||||
|
|
||||||
|
@@ -55,12 +55,12 @@ pub enum InvoiceStatus {
|
|||||||
Expired,
|
Expired,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for InvoiceStatus {
|
impl std::fmt::Display for InvoiceStatus {
|
||||||
fn to_string(&self) -> String {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
InvoiceStatus::Paid => "Paid".to_string(),
|
InvoiceStatus::Paid => write!(f, "Paid"),
|
||||||
InvoiceStatus::Unpaid => "Unpaid".to_string(),
|
InvoiceStatus::Unpaid => write!(f, "Unpaid"),
|
||||||
InvoiceStatus::Expired => "Expired".to_string(),
|
InvoiceStatus::Expired => write!(f, "Expired"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -177,28 +177,25 @@ ON CONFLICT (id) DO NOTHING"#,
|
|||||||
let tag_val = &tag[1];
|
let tag_val = &tag[1];
|
||||||
// only single-char tags are searchable
|
// only single-char tags are searchable
|
||||||
let tag_char_opt = single_char_tagname(tag_name);
|
let tag_char_opt = single_char_tagname(tag_name);
|
||||||
match &tag_char_opt {
|
if tag_char_opt.is_some() {
|
||||||
Some(_) => {
|
// if tag value is lowercase hex;
|
||||||
// if tag value is lowercase hex;
|
if is_lower_hex(tag_val) && (tag_val.len() % 2 == 0) {
|
||||||
if is_lower_hex(tag_val) && (tag_val.len() % 2 == 0) {
|
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, NULL, $3) \
|
||||||
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, NULL, $3) \
|
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
|
||||||
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
|
|
||||||
.bind(&id_blob)
|
.bind(&id_blob)
|
||||||
.bind(tag_name)
|
.bind(tag_name)
|
||||||
.bind(hex::decode(tag_val).ok())
|
.bind(hex::decode(tag_val).ok())
|
||||||
.execute(&mut tx)
|
.execute(&mut tx)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, $3, NULL) \
|
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, $3, NULL) \
|
||||||
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
|
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
|
||||||
.bind(&id_blob)
|
.bind(&id_blob)
|
||||||
.bind(tag_name)
|
.bind(tag_name)
|
||||||
.bind(tag_val.as_bytes())
|
.bind(tag_val.as_bytes())
|
||||||
.execute(&mut tx)
|
.execute(&mut tx)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -156,14 +156,11 @@ impl SqliteRepo {
|
|||||||
let tagval = &tag[1];
|
let tagval = &tag[1];
|
||||||
// only single-char tags are searchable
|
// only single-char tags are searchable
|
||||||
let tagchar_opt = single_char_tagname(tagname);
|
let tagchar_opt = single_char_tagname(tagname);
|
||||||
match &tagchar_opt {
|
if tagchar_opt.is_some() {
|
||||||
Some(_) => {
|
tx.execute(
|
||||||
tx.execute(
|
"INSERT OR IGNORE INTO tag (event_id, name, value, kind, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||||
"INSERT OR IGNORE INTO tag (event_id, name, value, kind, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
|
params![ev_id, &tagname, &tagval, e.kind, e.created_at],
|
||||||
params![ev_id, &tagname, &tagval, e.kind, e.created_at],
|
)?;
|
||||||
)?;
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,7 @@ PRAGMA user_version = {};
|
|||||||
-- Event Table
|
-- Event Table
|
||||||
CREATE TABLE IF NOT EXISTS event (
|
CREATE TABLE IF NOT EXISTS event (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
event_hash BLOB NOT NULL, -- 4-byte hash
|
event_hash BLOB NOT NULL, -- 32-byte SHA256 hash
|
||||||
first_seen INTEGER NOT NULL, -- when the event was first seen (not authored!) (seconds since 1970)
|
first_seen INTEGER NOT NULL, -- when the event was first seen (not authored!) (seconds since 1970)
|
||||||
created_at INTEGER NOT NULL, -- when the event was authored
|
created_at INTEGER NOT NULL, -- when the event was authored
|
||||||
expires_at INTEGER, -- when the event expires and may be deleted
|
expires_at INTEGER, -- when the event expires and may be deleted
|
||||||
|
@@ -202,7 +202,7 @@ async fn handle_web_request(
|
|||||||
.header("Content-Type", "text/html; charset=UTF-8")
|
.header("Content-Type", "text/html; charset=UTF-8")
|
||||||
.body(Body::from(file_content))
|
.body(Body::from(file_content))
|
||||||
.expect("request builder"));
|
.expect("request builder"));
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Failed to read relay_page file: {}. Will use default", err);
|
error!("Failed to read relay_page file: {}. Will use default", err);
|
||||||
}
|
}
|
||||||
@@ -920,7 +920,7 @@ pub fn start_server(settings: &Settings, shutdown_rx: MpscReceiver<()>) -> Resul
|
|||||||
info!("starting payment process ...");
|
info!("starting payment process ...");
|
||||||
p.run().await;
|
p.run().await;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to start payment process {e}");
|
error!("Failed to start payment process {e}");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
@@ -45,8 +45,8 @@ pub struct ReqFilter {
|
|||||||
|
|
||||||
impl Serialize for ReqFilter {
|
impl Serialize for ReqFilter {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
let mut map = serializer.serialize_map(None)?;
|
let mut map = serializer.serialize_map(None)?;
|
||||||
if let Some(ids) = &self.ids {
|
if let Some(ids) = &self.ids {
|
||||||
@@ -80,8 +80,8 @@ impl Serialize for ReqFilter {
|
|||||||
|
|
||||||
impl<'de> Deserialize<'de> for ReqFilter {
|
impl<'de> Deserialize<'de> for ReqFilter {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<ReqFilter, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<ReqFilter, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let received: Value = Deserialize::deserialize(deserializer)?;
|
let received: Value = Deserialize::deserialize(deserializer)?;
|
||||||
let filter = received.as_object().ok_or_else(|| {
|
let filter = received.as_object().ok_or_else(|| {
|
||||||
@@ -184,8 +184,8 @@ impl<'de> Deserialize<'de> for Subscription {
|
|||||||
/// Custom deserializer for subscriptions, which have a more
|
/// Custom deserializer for subscriptions, which have a more
|
||||||
/// complex structure than the other message types.
|
/// complex structure than the other message types.
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Subscription, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Subscription, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let mut v: Value = Deserialize::deserialize(deserializer)?;
|
let mut v: Value = Deserialize::deserialize(deserializer)?;
|
||||||
// this should be a 3-or-more element array.
|
// this should be a 3-or-more element array.
|
||||||
@@ -673,11 +673,26 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn is_scraper() -> Result<()> {
|
fn is_scraper() -> Result<()> {
|
||||||
assert!(serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"kinds": [1984],"since": 123,"limit":1}]"#)?.is_scraper());
|
assert!(serde_json::from_str::<Subscription>(
|
||||||
assert!(serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"kinds": [1984]},{"kinds": [1984],"authors":["aaaa"]}]"#)?.is_scraper());
|
r#"["REQ","some-id",{"kinds": [1984],"since": 123,"limit":1}]"#
|
||||||
assert!(!serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"kinds": [1984],"authors":["aaaa"]}]"#)?.is_scraper());
|
)?
|
||||||
assert!(!serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"ids": ["aaaa"]}]"#)?.is_scraper());
|
.is_scraper());
|
||||||
assert!(!serde_json::from_str::<Subscription>(r##"["REQ","some-id",{"#p": ["aaaa"],"kinds":[1,4]}]"##)?.is_scraper());
|
assert!(serde_json::from_str::<Subscription>(
|
||||||
|
r#"["REQ","some-id",{"kinds": [1984]},{"kinds": [1984],"authors":["aaaa"]}]"#
|
||||||
|
)?
|
||||||
|
.is_scraper());
|
||||||
|
assert!(!serde_json::from_str::<Subscription>(
|
||||||
|
r#"["REQ","some-id",{"kinds": [1984],"authors":["aaaa"]}]"#
|
||||||
|
)?
|
||||||
|
.is_scraper());
|
||||||
|
assert!(
|
||||||
|
!serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"ids": ["aaaa"]}]"#)?
|
||||||
|
.is_scraper()
|
||||||
|
);
|
||||||
|
assert!(!serde_json::from_str::<Subscription>(
|
||||||
|
r##"["REQ","some-id",{"#p": ["aaaa"],"kinds":[1,4]}]"##
|
||||||
|
)?
|
||||||
|
.is_scraper());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user