diff --git a/Cargo.lock b/Cargo.lock index 2c8df2f..22f571c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -497,6 +497,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "windows-sys", +] + [[package]] name = "flate2" version = "1.0.28" @@ -749,6 +761,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -936,6 +957,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "itoap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8" + [[package]] name = "js-sys" version = "0.3.64" @@ -1564,6 +1591,44 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "sailfish" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a03fcade08eb837d7ba0d8f775f1fe6cddb00d413de0c655ab2b93e821a0eb" +dependencies = [ + "itoap", + "ryu", + "sailfish-macros", + "version_check", +] + +[[package]] +name = "sailfish-compiler" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cf1deecb07747a1f7ab55745edcc406875db007602b037af5fcf59f40dd2005" +dependencies = [ + "filetime", + "home", + "memchr", + "proc-macro2", + "quote", + "serde", + "syn 2.0.28", + "toml", +] + +[[package]] +name = "sailfish-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e5946289d6daa26cde6ffb06678d7a8ba276ec6a7a55d36f64b35e7f644d22" +dependencies = [ + "proc-macro2", + "sailfish-compiler", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1676,6 +1741,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1759,6 +1833,7 @@ dependencies = [ "rusqlite", "rusqlite_migration", "rustls 0.21.6", + "sailfish", "sea-query", "sea-query-rusqlite", "serde", @@ -1987,11 +2062,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2000,6 +2090,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 42ed8ef..159f4d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ rusqlite_migration = "1.0.2" nostr = "0.27.0" nostr-database = "0.27.0" regex = "1.9.5" +sailfish = "0.7.0" sea-query = { version = "0.30.4", features = ["backend-sqlite", "thread-safe"] } sea-query-rusqlite = { version="0", features = [ "with-chrono", diff --git a/flake.nix b/flake.nix index 80ce26b..ded426f 100644 --- a/flake.nix +++ b/flake.nix @@ -13,6 +13,7 @@ naersk' = pkgs.callPackage naersk { }; wwwPath = "www"; + templatesPath = "templates"; in rec { # For `nix build` & `nix run`: @@ -24,6 +25,7 @@ mkdir -p $out/templates mkdir -p $out/www cp -r ${wwwPath} $out/ + cp -r ${templatesPath} $out/ ''; }; @@ -50,8 +52,6 @@ env = { DATABASE_URL = "/tmp/sqlite.db"; ADMIN_PUBKEY = "npub1m2w0ckmkgj4wtvl8muwjynh56j3qd4nddca4exdg4mdrkepvfnhsmusy54"; - CONFIG_ENABLE_AUTH = "false"; - CONFIG_RELAY_URL = "ws://0.0.0.0:8080"; RUST_BACKTRACE = 1; RUST_LOG = "debug"; }; diff --git a/modules/sneedstr.nix b/modules/sneedstr.nix index 1768b09..4d901f5 100644 --- a/modules/sneedstr.nix +++ b/modules/sneedstr.nix @@ -21,11 +21,6 @@ in { 'npub' of the administrator account. Must be defined! ''; }; - enableAuth = mkOption { - type = types.bool; - default = false; - description = "Require NIP-42 Authentication for REQ and EVENT"; - }; sslEnable = mkEnableOption "Whether to enable ACME SSL for nginx proxy"; hostAddress = mkOption { type = types.nullOr types.str; @@ -41,10 +36,6 @@ in { Local nixos-container ip address ''; }; - relayUrl = mkOption { - type = types.str; - description = "Relay URL that will be used for NIP-42 AUTH validation"; - }; }; config = mkIf cfg.enable { @@ -73,8 +64,6 @@ in { environment = { DATABASE_URL = "${DB_PATH}/sneedstr.db"; ADMIN_PUBKEY = cfg.adminPubkey; - CONFIG_ENABLE_AUTH = boolToString cfg.enableAuth; - CONFIG_RELAY_URL = cfg.relayUrl; }; startLimitBurst = 1; startLimitIntervalSec = 10; @@ -122,10 +111,6 @@ in { proxyWebsockets = true; # needed if you need to use WebSocket recommendedProxySettings = true; }; - locations."/register" = { - proxyPass = "http://${cfg.localAddress}:8085"; - recommendedProxySettings = true; - }; }; }; }; diff --git a/src/bussy/mod.rs b/src/bussy/mod.rs index 738293d..e39ce40 100644 --- a/src/bussy/mod.rs +++ b/src/bussy/mod.rs @@ -1,16 +1,15 @@ use crate::{ noose::sled::BanInfo, - noose::user::{User, UserRow, Nip05Profile}, - utils::{error::Error, structs::Subscription}, usernames::dto::UserBody, + noose::user::{User, UserRow}, + utils::{error::Error, structs::Subscription}, }; use nostr::secp256k1::XOnlyPublicKey; -use std::{collections::{HashMap, BTreeSet}, fmt::Debug}; +use std::{collections::HashMap, fmt::Debug}; use tokio::sync::{broadcast, Mutex}; pub mod channels { pub static MSG_NOOSE: &str = "MSG_NOOSE"; pub static MSG_NIP05: &str = "MSG_NIP05"; - pub static MSG_AUTH: &str = "MSG_AUTH"; pub static MSG_RELAY: &str = "MSG_RELAY"; pub static MSG_PIPELINE: &str = "MSG_PIPELINE"; pub static MSG_SLED: &str = "MSG_SLED"; @@ -19,21 +18,18 @@ pub mod channels { #[derive(Debug, Clone, PartialEq)] pub enum Command { // DbRequest - // --- Req DbReqWriteEvent(/* client_id */ uuid::Uuid, Box), DbReqFindEvent(/* client_id*/ uuid::Uuid, Subscription), DbReqDeleteEvents(/* client_id*/ uuid::Uuid, Box), DbReqEventCounts(/* client_id*/ uuid::Uuid, Subscription), + // Old messages DbReqInsertUser(UserRow), + DbReqGetUser(User), DbReqCreateAccount(XOnlyPublicKey, String, String), DbReqGetAccount(String), DbReqClear, - // NIP-05 related messages - DbReqGetNIP05(String), - DbReqCreateNIP05(UserBody), - // --- Res - DbResNIP05(Nip05Profile), + // DbResponse DbResRelayMessages( /* client_id*/ uuid::Uuid, /* Vec */ Vec, @@ -42,48 +38,26 @@ pub enum Command { DbResOk, DbResOkWithStatus(/* client_id */ uuid::Uuid, nostr::RelayMessage), DbResAccount, // TODO: Add Account DTO as a param + DbResUser(UserRow), DbResEventCounts(/* client_id */ uuid::Uuid, nostr::RelayMessage), - - // Noose Profile - // --- Req - DbReqGetProfile(XOnlyPublicKey), - DbReqAddContact(/* profile_pub_key */ XOnlyPublicKey, /* contact_pub_key*/ XOnlyPublicKey), - DbReqRemoveContact(/* profile_pub_key */ XOnlyPublicKey, /* contact_pub_key*/ XOnlyPublicKey), - DbReqGetContactPubkeys(XOnlyPublicKey), - DbReqGetContacts(XOnlyPublicKey), - // --- Res - DbResGetProfile(nostr_database::Profile), - DbResGetContactPubkeys(Vec), - DbResGetContacts(BTreeSet), - // Event Pipeline - // --- Req PipelineReqEvent(/* client_id */ uuid::Uuid, Box), - // --- Res PipelineResRelayMessageOk(/* client_id */ uuid::Uuid, nostr::RelayMessage), PipelineResStreamOutEvent(Box), PipelineResOk, - + // Subscription Errors + ClientSubscriptionError(/* error message */ String), // Sled - // --- Req SledReqBanUser(Box), SledReqBanInfo(/* pubkey */ String), SledReqUnbanUser(/* pubkey */ String), SledReqGetBans, - // --- Res SledResBan(Option), SledResBans(Vec), - SledResSuccess(bool), - + SledResSuccess(bool), // Other - ServiceRegistrationRequired(/* client_id */ uuid::Uuid, nostr::RelayMessage), Str(String), ServiceError(Error), - - // Subscription Errors - ClientSubscriptionError(/* error message */ String), - - // --- Noop Noop, } @@ -112,7 +86,7 @@ impl PubSub { } pub async fn subscribe(&self, topic: &str) -> broadcast::Receiver { - let (tx, _rx) = broadcast::channel(20_000); // 20000 is the channel capacity + let (tx, _rx) = broadcast::channel(32); // 32 is the channel capacity let mut subscribers = self.subscribers.lock().await; subscribers .entry(topic.to_string()) diff --git a/src/noose/db.rs b/src/noose/db.rs index fe48413..cb56a8a 100644 --- a/src/noose/db.rs +++ b/src/noose/db.rs @@ -1,58 +1,17 @@ use crate::{ bussy::PubSub, - usernames::dto::UserBody, utils::{error::Error, structs::Subscription}, }; -use nostr::{secp256k1::XOnlyPublicKey, Event, RelayMessage}; -use nostr_database::Profile; -use std::{collections::BTreeSet, sync::Arc}; +use nostr::{Event, RelayMessage}; +use std::sync::Arc; -use super::user::Nip05Profile; - -/// Handle core nostr events pub trait Noose: Send + Sync { - /// Start event listener async fn start(&mut self, pubsub: Arc) -> Result<(), Error>; - /// Save event in the Database async fn write_event(&self, event: Box) -> Result; - /// Find events by subscription async fn find_event(&self, subscription: Subscription) -> Result, Error>; - /// Get event counts by subscription async fn counts(&self, subscription: Subscription) -> Result; - - /// Get NIP-05 of the registered User by 'username' - async fn get_nip05(&self, username: String) -> Result; - - /// Create new NIP-05 for the User - async fn create_nip05(&self, user: UserBody) -> Result<(), Error>; - - /// Get Profile by public key - async fn profile(&self, public_key: XOnlyPublicKey) -> Result; - - /// Add new contact to Profile - async fn add_contact( - &self, - profile_public_key: XOnlyPublicKey, - contact_public_key: XOnlyPublicKey, - ) -> Result<(), Error>; - - /// Remove contact from the Profile - async fn remove_contact( - &self, - profile_public_key: XOnlyPublicKey, - contact_public_key: XOnlyPublicKey, - ) -> Result<(), Error>; - - /// Get Profile contats (pubkeys) - async fn contacts_public_keys( - &self, - public_key: XOnlyPublicKey, - ) -> Result, Error>; - - /// Get Profie contacts list - async fn contacts(&self, public_key: XOnlyPublicKey) -> Result, Error>; } diff --git a/src/noose/migrations/1706575155557_nip05.sql b/src/noose/migrations/1706575155557_nip05.sql deleted file mode 100644 index cb0314e..0000000 --- a/src/noose/migrations/1706575155557_nip05.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE nip05 ( - pubkey TEXT PRIMARY KEY, - username TEXT NOT NULL UNIQUE, - relays TEXT, - joined_at INTEGER NOT NULL -); - -CREATE INDEX idx_nip05_pubkey ON nip05 (pubkey); -CREATE INDEX idx_nip05_username ON nip05 (username); diff --git a/src/noose/migrations/1707327016995_nip42_profile.sql b/src/noose/migrations/1707327016995_nip42_profile.sql deleted file mode 100644 index 8a857d2..0000000 --- a/src/noose/migrations/1707327016995_nip42_profile.sql +++ /dev/null @@ -1,27 +0,0 @@ -CREATE TABLE profiles ( - pubkey TEXT PRIMARY KEY -); - -CREATE INDEX idx_profiles_pubkey ON profiles (pubkey); - -create TABLE contacts ( - profile TEXT REFERENCES profiles(pubkey) ON DELETE CASCADE, - contact TEXT REFERENCES profiles(pubkey) ON DELETE CASCADE -); - -CREATE TABLE metadata ( - pubkey TEXT REFERENCES profiles(pubkey) ON DELETE CASCADE, - name TEXT, - display_name TEXT, - about TEXT, - website TEXT, - picture TEXT, - banner TEXT, - nip05 TEXT, - lud06 TEXT, - lud16 TEXT, - custom TEXT -); - -CREATE INDEX idx_metadata_profiles_pubkey ON metadata (pubkey); - diff --git a/src/noose/migrations/mod.rs b/src/noose/migrations/mod.rs index de317ca..b9c7fdd 100644 --- a/src/noose/migrations/mod.rs +++ b/src/noose/migrations/mod.rs @@ -11,8 +11,7 @@ impl MigrationRunner { let m_events_fts = include_str!("./1697410223576_events_fts.sql"); let m_users = include_str!("./1697410294265_users.sql"); let m_unattached_media = include_str!("./1697410480767_unattached_media.sql"); - let m_nip05 = include_str!("./1706575155557_nip05.sql"); - let m_nip42 = include_str!("./1707327016995_nip42_profile.sql"); + let m_pragma = include_str!("./1697410424624_pragma.sql"); let migrations = Migrations::new(vec![ M::up(m_create_events), @@ -21,8 +20,7 @@ impl MigrationRunner { M::up(m_events_fts), M::up(m_users), M::up(m_unattached_media), - M::up(m_nip05), - M::up(m_nip42), + M::up(m_pragma), ]); match migrations.to_latest(connection) { diff --git a/src/noose/mod.rs b/src/noose/mod.rs index cf386db..8c260c0 100644 --- a/src/noose/mod.rs +++ b/src/noose/mod.rs @@ -7,7 +7,6 @@ mod migrations; pub mod pipeline; pub mod sled; mod sqlite; -mod sqlite_tables; pub mod user; pub fn start(context: Context) { diff --git a/src/noose/pipeline.rs b/src/noose/pipeline.rs index b3c24c6..0b0e189 100644 --- a/src/noose/pipeline.rs +++ b/src/noose/pipeline.rs @@ -22,7 +22,7 @@ impl Pipeline { let channel; let command = match message.content { Command::PipelineReqEvent(client_id, event) => { - match self.handle_event(client_id, event).await { + match self.handle_event(client_id, event.clone()).await { Ok(_) => { log::info!("[Pipeline] handle_event completed"); channel = message.source; diff --git a/src/noose/sqlite.rs b/src/noose/sqlite.rs index 821faed..189dff5 100644 --- a/src/noose/sqlite.rs +++ b/src/noose/sqlite.rs @@ -1,27 +1,197 @@ -use super::{ - db::Noose, - migrations::MigrationRunner, - user::{Nip05Profile, Nip05Table, UserRow}, -}; +use super::{db::Noose, migrations::MigrationRunner}; use crate::{ bussy::{channels, Command, Message, PubSub}, - usernames::dto::UserBody, utils::{config::Config as ServiceConfig, error::Error, structs::Subscription}, }; use async_trait::async_trait; use deadpool_sqlite::{Config, Object, Pool, Runtime}; use nostr::{ - nips::nip01::Coordinate, secp256k1::XOnlyPublicKey, Event, EventId, Filter, JsonUtil, - RelayMessage, TagKind, Timestamp, Url, + nips::nip01::Coordinate, Event, EventId, Filter, RelayMessage, TagKind, Timestamp, Url, }; use nostr_database::{Backend, DatabaseOptions, NostrDatabase, Order}; - +use rusqlite::Row; use sea_query::{extension::sqlite::SqliteExpr, Order as SqOrder, Query, SqliteQueryBuilder}; use sea_query_rusqlite::RusqliteBinder; use std::sync::Arc; use std::{collections::HashSet, str::FromStr}; -use crate::noose::sqlite_tables::*; +#[derive(Debug, Clone)] +struct EventRow { + id: String, + pubkey: String, + created_at: i64, + kind: i64, + tags: String, + sig: String, + content: String, +} + +impl From<&Row<'_>> for EventRow { + fn from(row: &Row) -> Self { + let id: String = row.get("id").unwrap(); + let pubkey: String = row.get("pubkey").unwrap(); + let created_at: i64 = row.get("created_at").unwrap(); + let kind: i64 = row.get("kind").unwrap(); + let tags: String = row.get("tags").unwrap(); + let sig: String = row.get("sig").unwrap(); + let content: String = row.get("content").unwrap(); + + Self { + id, + pubkey, + created_at, + kind, + tags, + sig, + content, + } + } +} + +impl From<&EventRow> for Event { + fn from(row: &EventRow) -> Self { + row.to_event() + } +} + +impl EventRow { + pub fn to_event(&self) -> Event { + let tags: Vec> = serde_json::from_str(&self.tags).unwrap(); + + let event_json = serde_json::json!( + { + "id": self.id, + "content": self.content, + "created_at": self.created_at, + "kind": self.kind, + "pubkey": self.pubkey, + "sig": self.sig, + "tags": tags + } + ); + + Event::from_value(event_json).unwrap() + } +} + +enum EventsTable { + Table, + EventId, + Kind, + Pubkey, + Content, + CreatedAt, + Tags, + Sig, +} + +impl sea_query::Iden for EventsTable { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!( + s, + "{}", + match self { + Self::Table => "events", + Self::EventId => "id", + Self::Kind => "kind", + Self::Pubkey => "pubkey", + Self::Content => "content", + Self::CreatedAt => "created_at", + Self::Tags => "tags", + Self::Sig => "sig", + } + ) + .unwrap() + } +} + +enum EventsFTSTable { + Table, + EventId, + Content, +} + +impl sea_query::Iden for EventsFTSTable { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!( + s, + "{}", + match self { + Self::Table => "events_fts", + Self::EventId => "id", + Self::Content => "content", + } + ) + .unwrap() + } +} + +enum TagsTable { + Table, + Tag, + Value, + EventId, +} + +impl sea_query::Iden for TagsTable { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!( + s, + "{}", + match self { + Self::Table => "tags", + Self::Tag => "tag", + Self::Value => "value", + Self::EventId => "event_id", + } + ) + .unwrap() + } +} + +// enum DeletedCoordinatesTable { +// Table, +// Coordinate, +// CreatedAt, +// } + +// impl sea_query::Iden for DeletedCoordinatesTable { +// fn unquoted(&self, s: &mut dyn std::fmt::Write) { +// write!( +// s, +// "{}", +// match self { +// Self::Table => "deleted_coordinates", +// Self::Coordinate => "coordinate", +// Self::CreatedAt => "created_at", +// } +// ) +// .unwrap() +// } +// } + +enum EventSeenByRelaysTable { + Table, + Id, + EventId, + RelayURL, +} + +impl sea_query::Iden for EventSeenByRelaysTable { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!( + s, + "{}", + match self { + Self::Table => "event_seen_by_relays", + Self::Id => "id", + Self::EventId => "event_id", + Self::RelayURL => "relay_url", + } + ) + .unwrap() + } +} #[derive(Debug)] pub struct NostrSqlite { @@ -42,22 +212,7 @@ impl NostrSqlite { async fn run_migrations(pool: &Pool) -> bool { let connection = pool.get().await.unwrap(); - connection.interact(MigrationRunner::up).await.unwrap(); - connection - .interact(|conn| { - conn.pragma_update(None, "encoding", "UTF-8").unwrap(); - conn.pragma_update(None, "journal_mode", "WAL").unwrap(); - conn.pragma_update(None, "foreign_keys", "ON").unwrap(); - conn.pragma_update(None, "auto_vacuum", "FULL").unwrap(); - conn.pragma_update(None, "journal_size_limit", "32768") - .unwrap(); - conn.pragma_update(None, "mmap_size", "17179869184") - .unwrap(); - }) - .await - .unwrap(); - - true + connection.interact(MigrationRunner::up).await.unwrap() } async fn get_connection(&self) -> Result { @@ -341,159 +496,86 @@ impl NostrSqlite { return Ok(false); } - - match tx.commit() { - Ok(_) => return Ok(true), - Err(err) => { - log::error!("Error during transaction commit: {}", err); - return Ok(false); - } - } - } - - if event.kind() == nostr::Kind::Metadata { - // Try to delete old profile first - let (sql, value) = Query::delete() - .from_table(ProfilesTable::Table) - .and_where(sea_query::Expr::col(ProfilesTable::Pubkey).eq(pubkey.clone())) - .build_rusqlite(SqliteQueryBuilder); - - if let Err(err) = tx.execute(sql.as_str(), &*value.as_params()) { - tx.rollback().unwrap(); - - log::debug!("Failed to delete old Profile record: {}", err); - return Ok(false); - } - - let (sql, value) = Query::insert() - .into_table(ProfilesTable::Table) - .columns([ProfilesTable::Pubkey]) - .values_panic([pubkey.clone().into()]) - .build_rusqlite(SqliteQueryBuilder); - - if let Err(err) = tx.execute(sql.as_str(), &*value.as_params()) { - tx.rollback().unwrap(); - - log::debug!("Failed to store Profile"); - return Ok(false); - } - - let Ok(metadata) = nostr::Metadata::from_json(content.clone()) else { - log::debug!("Failed to parse metadata"); - return Err(Error::bad_request( - "Unable to parse metadata from 'content'", - )); - }; - - let metadata_custom_fields = serde_json::to_string(&metadata.custom); - - let (sql, value) = Query::insert() - .into_table(MetadataTable::Table) + } else { + log::debug!("inserting new event in events"); + // Insert into Events table + let (sql, values) = Query::insert() + .into_table(EventsTable::Table) .columns([ - MetadataTable::Pubkey, - MetadataTable::Name, - MetadataTable::DisplayName, - MetadataTable::About, - MetadataTable::Website, - MetadataTable::Picture, - MetadataTable::Banner, - MetadataTable::Nip05, - MetadataTable::Lud06, - MetadataTable::Lud16, - MetadataTable::Custom, + EventsTable::EventId, + EventsTable::Content, + EventsTable::Kind, + EventsTable::Pubkey, + EventsTable::CreatedAt, + EventsTable::Tags, + EventsTable::Sig, ]) .values_panic([ - pubkey.clone().into(), - metadata.name.unwrap_or_default().into(), - metadata.display_name.unwrap_or_default().into(), - metadata.about.unwrap_or_default().into(), - metadata.website.unwrap_or_default().into(), - metadata.picture.unwrap_or_default().into(), - metadata.banner.unwrap_or_default().into(), - metadata.nip05.unwrap_or_default().into(), - metadata.lud06.unwrap_or_default().into(), - metadata.lud16.unwrap_or_default().into(), - metadata_custom_fields.unwrap_or_default().into(), + id.clone().into(), + content.clone().into(), + kind.into(), + pubkey.into(), + created_at.into(), + tags.into(), + sig.into(), ]) .build_rusqlite(SqliteQueryBuilder); - if let Err(err) = tx.execute(sql.as_str(), &*value.as_params()) { + if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) { + log::error!("Error inserting event into 'events' table: {}", err); + tx.rollback().unwrap(); + + return Ok(false); + }; + + // Insert into EventsFTS table + log::debug!("inserting new event into eventsFTS"); + let (sql, values) = Query::insert() + .into_table(EventsFTSTable::Table) + .columns([EventsFTSTable::EventId, EventsFTSTable::Content]) + .values_panic([id.clone().into(), content.into()]) + .build_rusqlite(SqliteQueryBuilder); + + if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) { + log::error!("Error inserting event into 'eventsFTS' table: {}", err); tx.rollback().unwrap(); - log::debug!("Failed to store Metadata: {}", err); return Ok(false); } - } - log::debug!("inserting new event in events"); - // Insert into Events table - let (sql, values) = Query::insert() - .into_table(EventsTable::Table) - .columns([ - EventsTable::EventId, - EventsTable::Content, - EventsTable::Kind, - EventsTable::Pubkey, - EventsTable::CreatedAt, - EventsTable::Tags, - EventsTable::Sig, - ]) - .values_panic([ - id.clone().into(), - content.clone().into(), - kind.into(), - pubkey.into(), - created_at.into(), - tags.into(), - sig.into(), - ]) - .build_rusqlite(SqliteQueryBuilder); - if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) { - log::error!("Error inserting event into 'events' table: {}", err); - tx.rollback().unwrap(); + // Insert into Tags table + log::debug!("inserting new event into tags"); + for tag in event.tags.clone() { + if Self::tag_is_indexable(&tag) { + let tag = tag.to_vec(); + if tag.len() >= 2 { + let tag_name = &tag[0]; + let tag_value = &tag[1]; + if tag_name.len() == 1 { + let (sql, values) = Query::insert() + .into_table(TagsTable::Table) + .columns([ + TagsTable::Tag, + TagsTable::Value, + TagsTable::EventId, + ]) + .values_panic([ + tag_name.into(), + tag_value.into(), + id.clone().into(), + ]) + .build_rusqlite(SqliteQueryBuilder); - return Ok(false); - }; + if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) + { + log::error!( + "Error inserting event into 'tags' table: {}", + err + ); + tx.rollback().unwrap(); - // Insert into EventsFTS table - log::debug!("inserting new event into eventsFTS"); - let (sql, values) = Query::insert() - .into_table(EventsFTSTable::Table) - .columns([EventsFTSTable::EventId, EventsFTSTable::Content]) - .values_panic([id.clone().into(), content.into()]) - .build_rusqlite(SqliteQueryBuilder); - - if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) { - log::error!("Error inserting event into 'eventsFTS' table: {}", err); - tx.rollback().unwrap(); - - return Ok(false); - } - - // Insert into Tags table - log::debug!("inserting new event into tags"); - for tag in event.tags.clone() { - if Self::tag_is_indexable(&tag) { - let tag = tag.to_vec(); - if tag.len() >= 2 { - let tag_name = &tag[0]; - let tag_value = &tag[1]; - if tag_name.len() == 1 { - let (sql, values) = Query::insert() - .into_table(TagsTable::Table) - .columns([TagsTable::Tag, TagsTable::Value, TagsTable::EventId]) - .values_panic([ - tag_name.into(), - tag_value.into(), - id.clone().into(), - ]) - .build_rusqlite(SqliteQueryBuilder); - - if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) { - log::error!("Error inserting event into 'tags' table: {}", err); - tx.rollback().unwrap(); - - return Ok(false); + return Ok(false); + } } } } @@ -762,12 +844,6 @@ impl NostrSqlite { let event = row.clone().to_event(); - if event.is_expired() { - return Err(Error::internal_with_message( - "Event has expired. Ignoring...", - )); - } - Ok(event) }) .await @@ -983,9 +1059,7 @@ impl NostrSqlite { let mut event_vec: Vec = vec![]; while let Ok(Some(row)) = rows.next() { let event = EventRow::from(row).to_event(); - if !event.is_expired() { - event_vec.push(event); - } + event_vec.push(event); } Ok(event_vec) @@ -1060,6 +1134,8 @@ impl NostrSqlite { let Ok(query_result) = connection .interact(move |conn| { let (sql, values) = sql_statement + .clear_selects() + .column(EventsTable::EventId) .order_by(EventsTable::CreatedAt, sq_order.to_owned()) .build_rusqlite(SqliteQueryBuilder); @@ -1068,10 +1144,9 @@ impl NostrSqlite { let mut event_vec: Vec = vec![]; while let Ok(Some(row)) = rows.next() { - let event = EventRow::from(row).to_event(); - if !event.is_expired() { - event_vec.push(event.id); - } + let event_id_string: String = row.get(0).unwrap(); + let event_id = EventId::from_str(&event_id_string).unwrap(); + event_vec.push(event_id); } Ok(event_vec) @@ -1101,39 +1176,35 @@ impl NostrSqlite { coordinate.kind, coordinate.pubkey, coordinate.identifier ) }; - + let Ok(query_result) = connection - .interact( - move |conn: &mut rusqlite::Connection| -> Result { - let (sql, value) = Query::select() - .from(EventsTable::Table) - .columns([EventsTable::EventId, EventsTable::CreatedAt]) - .left_join( - TagsTable::Table, - sea_query::Expr::col((TagsTable::Table, TagsTable::EventId)) - .equals((EventsTable::Table, EventsTable::EventId)), - ) - .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Tag)).eq("a")) - .and_where( - sea_query::Expr::col((TagsTable::Table, TagsTable::Value)).eq(ident), - ) - .and_where( - sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt)) - .gte(timestamp.as_i64()), - ) - .limit(1) - .build_rusqlite(SqliteQueryBuilder); + .interact(move |conn: &mut rusqlite::Connection| -> Result { + let (sql, value) = Query::select() + .from(EventsTable::Table) + .columns([EventsTable::EventId, EventsTable::CreatedAt]) + .left_join( + TagsTable::Table, + sea_query::Expr::col((TagsTable::Table, TagsTable::EventId)) + .equals((EventsTable::Table, EventsTable::EventId)), + ) + .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Tag)).eq("a")) + .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Value)).eq(ident)) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt)) + .gte(timestamp.as_i64()), + ) + .limit(1) + .build_rusqlite(SqliteQueryBuilder); - let mut stmt = conn.prepare(sql.as_str()).unwrap(); - let mut rows = stmt.query(&*value.as_params()).unwrap(); + let mut stmt = conn.prepare(sql.as_str()).unwrap(); + let mut rows = stmt.query(&*value.as_params()).unwrap(); - if let Ok(Some(record)) = rows.next() { - return Ok(false); - } + if let Ok(Some(record)) = rows.next() { + return Ok(false) + } - Ok(true) - }, - ) + Ok(true) + }) .await else { return Err(Error::internal_with_message( @@ -1143,147 +1214,6 @@ impl NostrSqlite { query_result } - - async fn get_nip05_profile(&self, username: String) -> Result { - let Ok(connection) = self.get_connection().await else { - return Err(Error::internal_with_message("Unable to get DB connection")); - }; - - let Ok(query_result) = connection - .interact( - move |conn: &mut rusqlite::Connection| -> Result { - let (sql, value) = Query::select() - .from(Nip05Table::Table) - .columns([ - Nip05Table::Pubkey, - Nip05Table::Username, - Nip05Table::Relays, - Nip05Table::JoinedAt, - ]) - .and_where(sea_query::Expr::col(Nip05Table::Username).eq(&username)) - .limit(1) - .build_rusqlite(SqliteQueryBuilder); - - let Ok(res) = conn.query_row(sql.as_str(), &*value.as_params(), |row| { - let nip05_row: UserRow = row.into(); - let nip05 = Nip05Profile::from(nip05_row); - - Ok(nip05) - }) else { - return Err(Error::not_found("user", username)); - }; - - Ok(res) - }, - ) - .await - else { - return Err(Error::internal_with_message( - "Failed to execute query 'get_nip05_profile'", - )); - }; - - query_result - } - - async fn get_profile( - &self, - public_key: XOnlyPublicKey, - ) -> Result { - let pk = public_key.to_string(); - - let Ok(connection) = self.get_connection().await else { - return Err(Error::internal_with_message("Unable to get DB connection")); - }; - - let Ok(query_result) = connection - .interact( - move |conn: &mut rusqlite::Connection| -> Result { - let (sql, value) = Query::select() - .from(MetadataTable::Table) - .columns([ - MetadataTable::Pubkey, - MetadataTable::Name, - MetadataTable::DisplayName, - MetadataTable::About, - MetadataTable::Website, - MetadataTable::Picture, - MetadataTable::Banner, - MetadataTable::Nip05, - MetadataTable::Lud06, - MetadataTable::Lud16, - MetadataTable::Custom, - ]) - .and_where(sea_query::Expr::col(MetadataTable::Pubkey).eq(&pk)) - .limit(1) - .build_rusqlite(SqliteQueryBuilder); - - let Ok(res) = conn.query_row(sql.as_str(), &*value.as_params(), |row| { - let profile_row: ProfileRow = row.into(); - let profile = nostr_database::Profile::from(&profile_row); - - Ok(profile) - }) else { - return Err(Error::not_found("user", pk)); - }; - - Ok(res) - }, - ) - .await - else { - return Err(Error::internal_with_message( - "Failed to execute query 'get_profile'", - )); - }; - - query_result - } - - async fn create_nip05_profile(&self, user: UserBody) -> Result<(), Error> { - let Ok(connection) = self.get_connection().await else { - return Err(Error::internal_with_message("Unable to get DB connection")); - }; - - let Ok(query_result) = connection - .interact( - move |conn: &mut rusqlite::Connection| -> Result<(), Error> { - let (sql, values) = Query::insert() - .into_table(Nip05Table::Table) - .columns([ - Nip05Table::Username, - Nip05Table::Pubkey, - Nip05Table::Relays, - Nip05Table::JoinedAt, - ]) - .values_panic([ - user.name.clone().into(), - user.get_pubkey().into(), - serde_json::to_string(&user.relays).unwrap().into(), - nostr::Timestamp::now().as_i64().into(), - ]) - .build_rusqlite(SqliteQueryBuilder); - - match conn.execute(sql.as_str(), &*values.as_params()) { - Ok(_) => Ok(()), - Err(e) => { - if e.to_string().contains("nip05.pubkey") { - return Err(Error::invalid_request_body("pubkey already taken")); - } - Err(Error::invalid_request_body("name already taken")) - } - } - }, - ) - .await - else { - return Err(Error::internal_with_message( - "Failed to execute query 'get_nip05_profile'", - )); - }; - - query_result - } } impl From for Error { @@ -1342,8 +1272,7 @@ impl NostrDatabase for NostrSqlite { coordinate: &Coordinate, timestamp: Timestamp, ) -> Result { - self.has_coordinate_been_deleted(coordinate, timestamp) - .await + self.has_coordinate_been_deleted(coordinate, timestamp).await } /// Set [`EventId`] as seen by relay @@ -1410,7 +1339,6 @@ impl Noose for NostrSqlite { while let Ok(message) = subscriber.recv().await { log::info!("[Noose] received message: {:?}", message); let command = match message.content { - // Relay Events Command::DbReqWriteEvent(client_id, event) => match self.write_event(event).await { Ok(status) => Command::DbResOkWithStatus(client_id, status), Err(e) => Command::ServiceError(e), @@ -1435,44 +1363,6 @@ impl Noose for NostrSqlite { Err(e) => Command::ServiceError(e), } } - // NIP-05 - Command::DbReqGetNIP05(username) => match self.get_nip05(username).await { - Ok(user) => Command::DbResNIP05(user), - Err(e) => Command::ServiceError(e), - }, - Command::DbReqCreateNIP05(user) => match self.create_nip05(user).await { - Ok(_) => Command::DbResOk, - Err(e) => Command::ServiceError(e), - }, - // NIP-42 - Command::DbReqGetProfile(pubkey) => match self.profile(pubkey).await { - Ok(profile) => Command::DbResGetProfile(profile), - Err(e) => Command::ServiceError(e), - }, - Command::DbReqAddContact(profile_pub_key, contact_pub_key) => { - match self.add_contact(profile_pub_key, contact_pub_key).await { - Ok(_) => Command::DbResOk, - Err(e) => Command::ServiceError(e), - } - } - Command::DbReqRemoveContact(profile_pub_key, contact_pub_key) => { - match self.remove_contact(profile_pub_key, contact_pub_key).await { - Ok(_) => Command::DbResOk, - Err(e) => Command::ServiceError(e), - } - } - Command::DbReqGetContactPubkeys(profile_pub_key) => { - match self.contacts_public_keys(profile_pub_key).await { - Ok(contact_pubkeys) => Command::DbResGetContactPubkeys(contact_pubkeys), - Err(e) => Command::ServiceError(e), - } - } - Command::DbReqGetContacts(profile_pub_key) => { - match self.contacts(profile_pub_key).await { - Ok(contacts) => Command::DbResGetContacts(contacts), - Err(e) => Command::ServiceError(e), - } - } _ => Command::Noop, }; if command != Command::Noop { @@ -1487,7 +1377,7 @@ impl Noose for NostrSqlite { message, channel ); - + pubsub.publish(channel, message).await; } } @@ -1496,6 +1386,7 @@ impl Noose for NostrSqlite { } async fn write_event(&self, event: Box) -> Result { + // TODO: Maybe do event validation and admin deletions here match self.save_event(&event).await { Ok(status) => { let relay_message = nostr::RelayMessage::ok(event.id, status, ""); @@ -1536,57 +1427,11 @@ impl Noose for NostrSqlite { Err(err) => Err(Error::internal_with_message(err.to_string())), } } - - async fn get_nip05(&self, username: String) -> Result { - self.get_nip05_profile(username).await - } - - async fn create_nip05(&self, user: UserBody) -> Result<(), Error> { - self.create_nip05_profile(user).await - } - - async fn profile( - &self, - public_key: nostr::prelude::XOnlyPublicKey, - ) -> Result { - self.get_profile(public_key).await - } - - async fn add_contact( - &self, - profile_public_key: nostr::prelude::XOnlyPublicKey, - contact_public_key: nostr::prelude::XOnlyPublicKey, - ) -> Result<(), Error> { - todo!() - } - - async fn remove_contact( - &self, - profile_public_key: nostr::prelude::XOnlyPublicKey, - contact_public_key: nostr::prelude::XOnlyPublicKey, - ) -> Result<(), Error> { - todo!() - } - - async fn contacts_public_keys( - &self, - public_key: nostr::prelude::XOnlyPublicKey, - ) -> Result, Error> { - todo!() - } - - async fn contacts( - &self, - public_key: nostr::prelude::XOnlyPublicKey, - ) -> Result, Error> { - todo!() - } } #[cfg(test)] mod tests { use crate::noose::sqlite::*; - use nostr::key::FromSkStr; use nostr::EventBuilder; #[tokio::test] @@ -1811,29 +1656,6 @@ mod tests { assert_eq!(result.len(), 1) } - #[tokio::test] - async fn save_metadata_event() { - let config = Arc::new(ServiceConfig::new()); - let db = NostrSqlite::new(config).await; - let secret_key = "nsec1g24e83hwj5gxl0hqxx9wllwcg9rrxthssv0mrxf4dv3lt8dc29yqrxf09p"; - let keys = nostr::Keys::from_sk_str(secret_key).unwrap(); - // Insert - let metadata = nostr::Metadata::new() - .name("Chuck") - .custom_field("feed", "seed"); - let event = nostr::EventBuilder::metadata(&metadata) - .to_event(&keys) - .unwrap(); - - let result = db.save_event(&event).await.unwrap(); - - // Find profile by pk - let pubkey = keys.public_key(); - let res = db.get_profile(pubkey).await.unwrap(); - - assert_eq!(res.name(), "Sneed"); - } - #[tokio::test] async fn save_event_with_a_tag() { let config = Arc::new(ServiceConfig::new()); @@ -1874,14 +1696,4 @@ mod tests { dbg!(res); } - - // #[tokio::test] - // async fn get_nip05() { - // let config = Arc::new(ServiceConfig::new()); - // let db = NostrSqlite::new(config).await; - - // let res = db.get_nip05("test".to_string()).await.unwrap(); - - // dbg!(serde_json::to_value(res).unwrap().to_string()); - // } } diff --git a/src/noose/sqlite_tables.rs b/src/noose/sqlite_tables.rs deleted file mode 100644 index 94b46f5..0000000 --- a/src/noose/sqlite_tables.rs +++ /dev/null @@ -1,322 +0,0 @@ -use std::collections::HashMap; - -use nostr::{key::FromPkStr, Event, Metadata}; -use nostr_database::Profile; -use rusqlite::Row; - -#[derive(Debug, Clone)] -pub struct EventRow { - pub id: String, - pub pubkey: String, - pub created_at: i64, - pub kind: i64, - pub tags: String, - pub sig: String, - pub content: String, -} - -impl From<&Row<'_>> for EventRow { - fn from(row: &Row) -> Self { - let id: String = row.get("id").unwrap(); - let pubkey: String = row.get("pubkey").unwrap(); - let created_at: i64 = row.get("created_at").unwrap(); - let kind: i64 = row.get("kind").unwrap(); - let tags: String = row.get("tags").unwrap(); - let sig: String = row.get("sig").unwrap(); - let content: String = row.get("content").unwrap(); - - Self { - id, - pubkey, - created_at, - kind, - tags, - sig, - content, - } - } -} - -impl From<&EventRow> for Event { - fn from(row: &EventRow) -> Self { - row.to_event() - } -} - -impl EventRow { - pub fn to_event(&self) -> Event { - let tags: Vec> = serde_json::from_str(&self.tags).unwrap(); - - let event_json = serde_json::json!( - { - "id": self.id, - "content": self.content, - "created_at": self.created_at, - "kind": self.kind, - "pubkey": self.pubkey, - "sig": self.sig, - "tags": tags - } - ); - - Event::from_value(event_json).unwrap() - } -} - -pub enum EventsTable { - Table, - EventId, - Kind, - Pubkey, - Content, - CreatedAt, - Tags, - Sig, -} - -impl sea_query::Iden for EventsTable { - fn unquoted(&self, s: &mut dyn std::fmt::Write) { - write!( - s, - "{}", - match self { - Self::Table => "events", - Self::EventId => "id", - Self::Kind => "kind", - Self::Pubkey => "pubkey", - Self::Content => "content", - Self::CreatedAt => "created_at", - Self::Tags => "tags", - Self::Sig => "sig", - } - ) - .unwrap() - } -} - -pub enum EventsFTSTable { - Table, - EventId, - Content, -} - -impl sea_query::Iden for EventsFTSTable { - fn unquoted(&self, s: &mut dyn std::fmt::Write) { - write!( - s, - "{}", - match self { - Self::Table => "events_fts", - Self::EventId => "id", - Self::Content => "content", - } - ) - .unwrap() - } -} - -pub enum TagsTable { - Table, - Tag, - Value, - EventId, -} - -impl sea_query::Iden for TagsTable { - fn unquoted(&self, s: &mut dyn std::fmt::Write) { - write!( - s, - "{}", - match self { - Self::Table => "tags", - Self::Tag => "tag", - Self::Value => "value", - Self::EventId => "event_id", - } - ) - .unwrap() - } -} - -pub enum EventSeenByRelaysTable { - Table, - Id, - EventId, - RelayURL, -} - -impl sea_query::Iden for EventSeenByRelaysTable { - fn unquoted(&self, s: &mut dyn std::fmt::Write) { - write!( - s, - "{}", - match self { - Self::Table => "event_seen_by_relays", - Self::Id => "id", - Self::EventId => "event_id", - Self::RelayURL => "relay_url", - } - ) - .unwrap() - } -} - -pub enum ProfilesTable { - Table, - Pubkey, -} - -impl sea_query::Iden for ProfilesTable { - fn unquoted(&self, s: &mut dyn std::fmt::Write) { - write!( - s, - "{}", - match self { - Self::Table => "profiles", - Self::Pubkey => "pubkey", - } - ) - .unwrap() - } -} - -pub enum ContactsTable { - Table, - Profile, - Contact, -} - -impl sea_query::Iden for ContactsTable { - fn unquoted(&self, s: &mut dyn std::fmt::Write) { - write!( - s, - "{}", - match self { - Self::Table => "contacts", - Self::Profile => "profile", - Self::Contact => "contact", - } - ) - .unwrap() - } -} - -pub enum MetadataTable { - Table, - Pubkey, - Name, - DisplayName, - About, - Website, - Picture, - Banner, - Nip05, - Lud06, - Lud16, - Custom, -} - -impl sea_query::Iden for MetadataTable { - fn unquoted(&self, s: &mut dyn std::fmt::Write) { - write!( - s, - "{}", - match self { - Self::Table => "metadata", - Self::Pubkey => "pubkey", - Self::Name => "name", - Self::DisplayName => "display_name", - Self::About => "about", - Self::Website => "website", - Self::Picture => "picture", - Self::Banner => "banner", - Self::Nip05 => "nip05", - Self::Lud06 => "lud06", - Self::Lud16 => "lud16", - Self::Custom => "custom", - } - ) - .unwrap() - } -} - -#[derive(Debug, Clone)] -pub struct ProfileRow { - pubkey: String, - // metadata - name: Option, - display_name: Option, - about: Option, - website: Option, - picture: Option, - banner: Option, - nip05: Option, - lud06: Option, - lud16: Option, - custom: Option, -} - -impl From<&Row<'_>> for ProfileRow { - fn from(row: &Row) -> Self { - let pubkey: String = row.get("pubkey").unwrap(); - let name: Option = row.get("name").unwrap_or_default(); - let display_name: Option = row.get("display_name").unwrap_or_default(); - let about = row.get("about").unwrap_or_default(); - let website: Option = row.get("website").unwrap_or_default(); - let picture: Option = row.get("picture").unwrap_or_default(); - let banner: Option = row.get("banner").unwrap_or_default(); - let nip05: Option = row.get("nip05").unwrap_or_default(); - let lud06: Option = row.get("lud06").unwrap_or_default(); - let lud16: Option = row.get("lud16").unwrap_or_default(); - let custom: Option = row.get("custom").unwrap_or_default(); - - Self { - pubkey, - name, - display_name, - about, - website, - picture, - banner, - nip05, - lud06, - lud16, - custom, - } - } -} - -impl From<&ProfileRow> for Profile { - fn from(row: &ProfileRow) -> Self { - let row = row.to_owned(); - // let f = nostr::EventBuilder::metadata( // Why am I creating this methods in Noose? Just store Kind 0 on a relay and think that the user is registered on the relay ffs - let keys = nostr::Keys::from_pk_str(&row.pubkey).unwrap(); - - let custom_fields: HashMap = match row.custom { - Some(fields_str) => { - let fields: HashMap = - serde_json::from_str(&fields_str).unwrap_or(HashMap::new()); - fields - } - None => { - let f: HashMap = HashMap::new(); - f - } - }; - - let metadata = Metadata { - name: row.name, - display_name: row.display_name, - about: row.about, - website: row.website, - picture: row.picture, - banner: row.banner, - nip05: row.nip05, - lud06: row.lud06, - lud16: row.lud16, - custom: custom_fields, - }; - - nostr_database::Profile::new(keys.public_key(), metadata) - } -} diff --git a/src/noose/user.rs b/src/noose/user.rs index d778b22..fe31020 100644 --- a/src/noose/user.rs +++ b/src/noose/user.rs @@ -1,103 +1,27 @@ +use chrono::Utc; use regex::Regex; -use rusqlite::Row; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use validator::{Validate, ValidationError}; lazy_static! { static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap(); } -pub enum Nip05Table { - Table, - Pubkey, - Username, - Relays, - JoinedAt, -} - -impl sea_query::Iden for Nip05Table { - fn unquoted(&self, s: &mut dyn std::fmt::Write) { - write!( - s, - "{}", - match self { - Self::Table => "nip05", - Self::Pubkey => "pubkey", - Self::Username => "username", - Self::Relays => "relays", - Self::JoinedAt => "joined_at", - } - ) - .unwrap() - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)] pub struct UserRow { pub pubkey: String, pub username: String, - relays: Vec, - joined_at: i64, + inserted_at: i64, + admin: bool, } impl UserRow { - pub fn new(pubkey: String, username: String, relays: Vec) -> Self { + pub fn new(pubkey: String, username: String, admin: bool) -> Self { Self { pubkey, username, - relays, - joined_at: nostr::Timestamp::now().as_i64(), - } - } -} - -impl From<&Row<'_>> for UserRow { - fn from(row: &Row) -> Self { - let pubkey: String = row.get("pubkey").unwrap(); - let username: String = row.get("username").unwrap(); - let relays_raw: String = row.get("relays").unwrap_or_default(); - let joined_at: i64 = row.get("joined_at").unwrap(); - - let relays: Vec = match serde_json::from_str(&relays_raw) { - Ok(val) => val, - Err(_) => vec![], - }; - - Self { - pubkey, - username, - relays, - joined_at, - } - } -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] -pub struct Nip05Profile { - names: HashMap, - #[serde(skip_serializing_if = "Option::is_none")] - relays: Option>>, -} - -impl From for Nip05Profile { - fn from(value: UserRow) -> Self { - let mut name: HashMap = HashMap::new(); - name.insert(value.username, value.pubkey.clone()); - - let relay = match value.relays.is_empty() { - true => None, - false => { - let mut relay: HashMap> = HashMap::new(); - relay.insert(value.pubkey.clone(), value.relays); - - Some(relay) - } - }; - - Self { - names: name, - relays: relay, + inserted_at: Utc::now().timestamp(), + admin, } } } @@ -117,33 +41,3 @@ pub fn validate_pubkey(value: &str) -> Result<(), ValidationError> { Err(_) => Err(ValidationError::new("Unable to parse pubkey")), } } - -#[cfg(test)] -mod tests { - use super::{Nip05Profile, UserRow}; - - #[test] - fn make_nip05() { - let user_row = UserRow::new( - "npub1m2w0ckmkgj4wtvl8muwjynh56j3qd4nddca4exdg4mdrkepvfnhsmusy54".to_string(), - "test_user".to_string(), - vec![], - ); - - let nip05 = Nip05Profile::from(user_row); - - dbg!(&nip05); - dbg!(serde_json::to_string(&nip05).unwrap()); - } - - #[test] - fn parse_relay_vec() { - let relays_raw = ""; - let relays: Vec = match serde_json::from_str(relays_raw) { - Ok(val) => val, - Err(_) => vec![], - }; - - dbg!(relays); - } -} diff --git a/src/relay/handler.rs b/src/relay/handler.rs index ea59ea2..4295254 100644 --- a/src/relay/handler.rs +++ b/src/relay/handler.rs @@ -11,12 +11,18 @@ pub async fn ws_handler( Ok(ws.on_upgrade(move |socket| ws::client_connection(socket, context, real_client_ip))) } -pub async fn relay_config(header: String, context: Context) -> Result { +pub async fn relay_config(header: String) -> Result { if header != "application/nostr+json" { Err(warp::reject::not_found()) } else { - let config = context.config.get_relay_config_json(); - - Ok(warp::reply::json(&config)) + let res = serde_json::json!({ + "contact": "klink@zhitno.st", + "name": "zhitno.st", + "description": "Very *special* nostr relay", + "supported_nips": [ 1, 9, 11, 12, 15, 16, 20, 22, 28, 33 ], + "software": "git+https://git.zhitno.st/Klink/sneedstr.git", + "version": "0.1.0" + }); + Ok(warp::reply::json(&res)) } } diff --git a/src/relay/routes.rs b/src/relay/routes.rs index 1607b2a..d30cfae 100644 --- a/src/relay/routes.rs +++ b/src/relay/routes.rs @@ -6,26 +6,25 @@ use warp::{Filter, Rejection, Reply}; pub fn routes(context: Context) -> impl Filter + Clone { let cors = warp::cors().allow_any_origin(); - static_files().or(index(context)).with(&cors) + static_files().or(index(context)).with(cors) } fn index(context: Context) -> impl Filter + Clone { // let real_client_ip = warp::header::optional::("X-Real-IP"); let real_client_ip = warp::addr::remote(); + let accept_application_json_header = warp::header::header("Accept"); + let cors = warp::cors().allow_any_origin(); - let relay_information_document_path = warp::path::end().and( - warp::header::header("Accept") - .and(with_context(context.clone())) - .and_then(handler::relay_config), - ); - let nostr_relay_path = warp::path::end().and( - warp::ws() - .and(with_context(context.clone())) - .and(real_client_ip) - .and_then(handler::ws_handler), - ); - - relay_information_document_path.or(nostr_relay_path) + warp::path::end().and( + accept_application_json_header + .and_then(handler::relay_config) + .with(&cors) + .or(warp::ws() + .and(with_context(context)) + .and(real_client_ip) + .and_then(handler::ws_handler) + .with(&cors)), + ) } fn static_files() -> impl Filter + Clone { diff --git a/src/relay/ws.rs b/src/relay/ws.rs index 3c103cc..30cd88a 100644 --- a/src/relay/ws.rs +++ b/src/relay/ws.rs @@ -94,17 +94,6 @@ pub async fn client_connection( } } }, - crate::bussy::Command::ServiceRegistrationRequired(client_id, relay_message) => { - if client.client_id == client_id { - if let Some(sender) = &client.client_connection { - if !sender.is_closed() { - log::info!("[Relay] client needs to be authenticated to make request: {}", relay_message.as_json()); - sender.send(Ok(Message::text(relay_message.as_json()))).unwrap(); - } - - } - } - }, _ => () } @@ -112,6 +101,13 @@ pub async fn client_connection( Some(message) = client_receiver.next() => { match message { Ok(message) => { + // ws_sender + // .send(message) + // .unwrap_or_else(|e| { + // log::error!("websocket send error: {}", e); + // }) + // .await; + match ws_sender.send(message).await { Ok(_) => (), Err(e) => { @@ -198,7 +194,7 @@ async fn socket_on_message(context: &Context, client: &mut Client, msg: Message) } }; - log::debug!( + log::info!( "[client {} - {}] message: {}", client.ip(), client.client_id, @@ -215,89 +211,23 @@ fn send(client: &Client, message: Message) { } } -fn needs_auth(context: &Context, client: &Client, event: Option<&Event>) -> bool { - if !context.config.auth_required() { - return false; - } - - if event.is_some() { - let event = event.unwrap(); - let admin_pk = context.config.get_admin_pubkey(); - if event.pubkey == *admin_pk { - return false; - } - - if event.kind() == nostr::Kind::Metadata { - return false; - } - } - - if !client.authenticated { - return true; - } - - false -} - async fn handle_msg(context: &Context, client: &mut Client, client_message: ClientMessage) { match client_message { - ClientMessage::Event(event) => { - if needs_auth(context, client, Some(&event)) { - request_auth(context, client).await; - return; - } - - handle_event(context, client, event).await - } + ClientMessage::Event(event) => handle_event(context, client, event).await, ClientMessage::Req { subscription_id, filters, - } => { - if needs_auth(context, client, None) { - request_auth(context, client).await; - return; - } - - handle_req(context, client, subscription_id, filters).await - } + } => handle_req(context, client, subscription_id, filters).await, ClientMessage::Count { subscription_id, filters, - } => { - if needs_auth(context, client, None) { - request_auth(context, client).await; - return; - } - - handle_count(context, client, subscription_id, filters).await - } + } => handle_count(context, client, subscription_id, filters).await, ClientMessage::Close(subscription_id) => handle_close(client, subscription_id).await, - ClientMessage::Auth(event) => handle_auth(context, client, event).await, - // Unhandled messages - _ => unhandled_message(context, client).await, + ClientMessage::Auth(event) => handle_auth(client, event).await, + _ => (), } } -async fn request_auth(context: &Context, client: &mut Client) { - let challenge = uuid::Uuid::new_v4().to_string(); - client.set_challenge(challenge.clone()); - - let auth_message = nostr::RelayMessage::auth(challenge); - context - .pubsub - .publish( - channels::MSG_RELAY, - crate::bussy::Message { - source: channels::MSG_RELAY, - content: crate::bussy::Command::ServiceRegistrationRequired( - client.client_id, - auth_message, - ), - }, - ) - .await; -} - async fn handle_event(context: &Context, client: &Client, event: Box) { log::debug!("handle_event is processing new event"); @@ -398,77 +328,14 @@ async fn handle_count( } async fn handle_close(client: &mut Client, subscription_id: SubscriptionId) { + // context.pubsub.send(new nostr event) then handle possible errors client.unsubscribe(subscription_id); + + // let message = Message::text("CLOSE not implemented"); + // send(client, message); } -async fn handle_auth(context: &Context, client: &mut Client, event: Box) { - let mut subscriber = context.pubsub.subscribe(channels::MSG_AUTH).await; - context - .pubsub - .publish( - channels::MSG_NOOSE, - crate::bussy::Message { - source: channels::MSG_AUTH, - content: crate::bussy::Command::DbReqGetProfile(event.pubkey), - }, - ) - .await; - - let mut message = nostr::RelayMessage::ok( - event.id, - client.authenticated, - "auth-required: User not registered", - ); - - let Ok(result) = subscriber.recv().await else { - context - .pubsub - .publish( - channels::MSG_RELAY, - crate::bussy::Message { - source: channels::MSG_RELAY, - content: crate::bussy::Command::DbResOkWithStatus(client.client_id, message), - }, - ) - .await; - return; - }; - - if let crate::bussy::Command::DbResGetProfile(profile) = result.content { - client.authenticate(context.config.get_relay_url(), &event); - - let client_status = format!("Client authenticated: {}", client.authenticated); - let status_message = if client.authenticated { - "" - } else { - "auth-required: we only accept events from registered users" - }; - - message = nostr::RelayMessage::ok(event.id, client.authenticated, status_message); - }; - - context - .pubsub - .publish( - channels::MSG_RELAY, - crate::bussy::Message { - source: channels::MSG_RELAY, - content: crate::bussy::Command::DbResOkWithStatus(client.client_id, message), - }, - ) - .await; -} - -async fn unhandled_message(context: &Context, client: &Client) { - let message = nostr::RelayMessage::notice("Unsupported Message"); - context - .pubsub - .publish( - channels::MSG_RELAY, - crate::bussy::Message { - source: channels::MSG_RELAY, - content: crate::bussy::Command::DbResOkWithStatus(client.client_id, message), - }, - ) - .await +async fn handle_auth(client: &Client, event: Box) { + let message = Message::text("AUTH not implemented"); + send(client, message); } diff --git a/src/usernames/dto/mod.rs b/src/usernames/dto/mod.rs index 9b7d1a2..eb8bc55 100644 --- a/src/usernames/dto/mod.rs +++ b/src/usernames/dto/mod.rs @@ -1,7 +1,9 @@ -use crate::usernames::validators::{validate_pubkey, validate_relays}; +use std::collections::HashMap; + +use crate::usernames::validators::validate_pubkey; use crate::utils::error::Error; -use nostr::key::XOnlyPublicKey; use nostr::prelude::*; +use nostr::{key::XOnlyPublicKey, Keys}; use regex::Regex; use serde::{Deserialize, Serialize}; use validator::Validate; @@ -10,26 +12,31 @@ lazy_static! { static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap(); } -#[derive(Serialize, Deserialize, Debug, Validate, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Validate)] pub struct UserBody { #[validate(length(min = 1), regex = "VALID_CHARACTERS")] pub name: String, #[validate(custom(function = "validate_pubkey"))] - pubkey: String, - #[validate(custom(function = "validate_relays"))] - pub relays: Vec + pub pubkey: String, } impl UserBody { - pub fn get_pubkey(&self) -> String { - nostr::Keys::from_pk_str(&self.pubkey).unwrap().public_key().to_string() + pub fn get_pubkey(&self) -> XOnlyPublicKey { + let keys = Keys::from_pk_str(&self.pubkey).unwrap(); + + keys.public_key() } } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Nip05 { + names: HashMap, +} + #[derive(Serialize, Deserialize, Debug, Validate, Clone)] pub struct UserQuery { #[validate(length(min = 1))] - pub name: String, + pub user: String, } #[derive(Serialize, Deserialize, Debug, Clone, Validate)] diff --git a/src/usernames/filter.rs b/src/usernames/filter.rs index c4e6864..b74c5a8 100644 --- a/src/usernames/filter.rs +++ b/src/usernames/filter.rs @@ -2,6 +2,10 @@ use crate::utils::error::Error; use validator::Validate; use warp::{Filter, Rejection}; +pub fn with_client_ip() {} + +pub fn with_user_body() {} + pub fn validate_body_filter( ) -> impl Filter + Copy { warp::body::json::().and_then(|query: T| async move { diff --git a/src/usernames/handler.rs b/src/usernames/handler.rs index a229aca..b281d2e 100644 --- a/src/usernames/handler.rs +++ b/src/usernames/handler.rs @@ -6,8 +6,6 @@ use crate::utils::structs::Context; use serde_json::json; use warp::{Rejection, Reply}; -use super::dto::UserBody; - pub async fn get_account( // account: Result, account: Account, @@ -47,9 +45,14 @@ pub async fn get_account( } pub async fn get_user(user_query: UserQuery, context: Context) -> Result { + let name = user_query.user; let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await; - let command = Command::DbReqGetNIP05(user_query.name); + let user = User { + name: Some(name), + pubkey: None, + }; + let command = Command::DbReqGetUser(user); context .pubsub .publish( @@ -62,44 +65,18 @@ pub async fn get_user(user_query: UserQuery, context: Context) -> Result { - let response = serde_json::to_value(profile).unwrap(); + Command::DbResUser(user) => { + response = json!({ + "names": { + user.username: user.pubkey + }, + "relays": {} + }); Ok(warp::reply::json(&response)) } - Command::ServiceError(e) => Err(warp::reject::custom(e)), - _ => Err(warp::reject::custom(Error::internal_with_message( - "Unhandeled message type", - ))), - } - } else { - Err(warp::reject::custom(Error::internal_with_message( - "Unhandeled message type", - ))) - } -} - -pub async fn create_user(user_body: UserBody, context: Context) -> Result { - let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await; - - let command = Command::DbReqCreateNIP05(user_body); - context - .pubsub - .publish( - channels::MSG_NOOSE, - Message { - source: channels::MSG_NIP05, - content: command, - }, - ) - .await; - - if let Ok(message) = subscriber.recv().await { - match message.content { - Command::DbResOk => { - Ok(warp::http::StatusCode::CREATED) - } - Command::ServiceError(e) => Err(warp::reject::custom(e)), + Command::ServiceError(e) => Ok(warp::reply::json(&response)), _ => Err(warp::reject::custom(Error::internal_with_message( "Unhandeled message type", ))), diff --git a/src/usernames/mod.rs b/src/usernames/mod.rs index d692e03..379fd9b 100644 --- a/src/usernames/mod.rs +++ b/src/usernames/mod.rs @@ -1,4 +1,4 @@ -// mod accounts; +mod accounts; pub mod dto; mod filter; mod handler; diff --git a/src/usernames/routes.rs b/src/usernames/routes.rs index ecc435e..96d8ad3 100644 --- a/src/usernames/routes.rs +++ b/src/usernames/routes.rs @@ -1,69 +1,49 @@ use crate::noose::user::User; -// use super::accounts::create_account; -use super::dto::{Account, UserBody, UserQuery}; +use super::accounts::create_account; +use super::dto::{Account, UserQuery}; use super::filter::{validate_body_filter, validate_query_filter}; -use super::handler::{create_user, get_account, get_user}; +use super::handler::{get_account, get_user}; use crate::utils::filter::with_context; use crate::utils::structs::Context; use warp::{Filter, Rejection, Reply}; pub fn routes(context: Context) -> impl Filter + Clone { - let cors = warp::cors().allow_any_origin(); let index = warp::path::end().map(|| warp::reply::html("

SNEED!

")); index .or(nip05_get(context.clone())) - .or(nip05_create(context.clone())) - .with(&cors) + .or(account_create(context.clone())) } -fn well_known(warp_method: M) -> impl Filter + Clone -where - M: (Filter) + Copy, -{ - warp_method.and(warp::path(".well-known")) -} - -fn nostr_well_known() -> impl Filter + Clone { - well_known(warp::get()).and(warp::path("nostr.json")) -} - -fn nip05_get(context: Context) -> impl Filter + Clone { - nostr_well_known() +pub fn nip05_get(context: Context) -> impl Filter + Clone { + warp::get() + .and(warp::path(".well-known")) + .and(warp::path("nostr.json")) .and(validate_query_filter::()) - .and(with_context(context.clone())) + .and(with_context(context)) .and_then(get_user) } -fn nip05_create(context: Context) -> impl Filter + Clone { - well_known(warp::post()) - .and(warp::path("nostr.json")) - .and(warp::body::content_length_limit(1024)) - .and(validate_body_filter::()) - .and(with_context(context.clone())) - .and_then(create_user) +pub fn account_create( + context: Context, +) -> impl Filter + Clone { + warp::path("account") + .and(warp::post()) + .and(validate_body_filter::()) + .and(with_context(context)) + .and_then(create_account) } -// pub fn account_create( -// context: Context, -// ) -> impl Filter + Clone { -// warp::path("account") -// .and(warp::post()) -// .and(validate_body_filter::()) -// .and(with_context(context)) -// .and_then(create_account) -// } - -// pub fn account_get( -// context: Context, -// ) -> impl Filter + Clone { -// warp::path("account") -// .and(warp::get()) -// .and(validate_body_filter::()) -// .and(with_context(context)) -// .and_then(get_account) -// } +pub fn account_get( + context: Context, +) -> impl Filter + Clone { + warp::path("account") + .and(warp::get()) + .and(validate_body_filter::()) + .and(with_context(context)) + .and_then(get_account) +} // pub fn account_update( // context: Context, diff --git a/src/usernames/validators.rs b/src/usernames/validators.rs index b6dbcb7..48f6cb9 100644 --- a/src/usernames/validators.rs +++ b/src/usernames/validators.rs @@ -1,7 +1,7 @@ use super::dto::AccountPubkey; use crate::utils::error::Error; use nostr::prelude::FromPkStr; -use validator::{Validate, ValidationError, validate_url}; +use validator::{Validate, ValidationError}; pub async fn validate_account_pubkey_query( account_pubkey: AccountPubkey, @@ -25,18 +25,6 @@ pub fn validate_pubkey(value: &str) -> Result<(), ValidationError> { match nostr::Keys::from_pk_str(value) { Ok(_) => Ok(()), - Err(_) => Err(ValidationError::new("Failed to parse pubkey")), + Err(_) => Err(ValidationError::new("Unable to parse pk_str")), } } - -pub fn validate_relays(relays: &Vec) -> Result<(), ValidationError> { - if relays.is_empty() { - return Ok(()) - } - - if relays.iter().all(validate_url) { - return Ok(()) - } - - Err(ValidationError::new("Relays have wrong url format")) -} diff --git a/src/utils/config.rs b/src/utils/config.rs index ea25463..3349957 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, str::FromStr}; +use std::path::PathBuf; use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey}; @@ -6,8 +6,6 @@ use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey}; pub struct Config { admin_pubkey: XOnlyPublicKey, db_path: PathBuf, - auth_required: bool, - relay_url: nostr::UncheckedUrl, } impl Default for Config { @@ -25,24 +23,13 @@ impl Config { .public_key(); let db_path = std::env::var("DATABASE_URL").map(PathBuf::from).unwrap(); - let auth_required: bool = std::env::var("CONFIG_ENABLE_AUTH") - .unwrap_or("false".to_string()) - .parse() - .unwrap(); - let relay_url = nostr::UncheckedUrl::from_str(&std::env::var("CONFIG_RELAY_URL").unwrap()).unwrap(); Self { admin_pubkey, db_path, - auth_required, - relay_url, } } - pub fn auth_required(&self) -> bool { - self.auth_required - } - pub fn get_admin_pubkey(&self) -> &XOnlyPublicKey { &self.admin_pubkey } @@ -51,18 +38,14 @@ impl Config { self.db_path.clone() } - pub fn get_relay_url(&self) -> nostr::UncheckedUrl { - self.relay_url.clone() - } - pub fn get_relay_config_json(&self) -> serde_json::Value { serde_json::json!({ "contact": "klink@zhitno.st", "name": "zhitno.st", "description": "Very *special* nostr relay", - "supported_nips": [ 1, 2, 4, 9, 11, 12, 15, 16, 20, 22, 28, 33, 40, 42, 45, 50 ], + "supported_nips": [ 1, 9, 11, 12, 15, 16, 20, 22, 28, 33, 45 ], "software": "git+https://git.zhitno.st/Klink/sneedstr.git", - "version": "0.1.1" + "version": "0.1.0" }) } } diff --git a/src/utils/error.rs b/src/utils/error.rs index 5812651..c801287 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -8,13 +8,13 @@ use std::{ use validator::ValidationErrors; use warp::{http::StatusCode, reject::Reject}; -const VERSION: &str = env!("CARGO_PKG_VERSION"); - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct Error { pub code: u16, pub message: String, - pub sneedstr_version: String, + /// Sneedstr version. + #[serde(skip_serializing_if = "Option::is_none")] + pub sneedstr_version: Option, } impl StdError for Error { @@ -32,7 +32,7 @@ impl Error { Self { code: code.as_u16(), message, - sneedstr_version: VERSION.to_string(), + sneedstr_version: None, } } @@ -54,11 +54,12 @@ impl Error { Self::new(StatusCode::BAD_REQUEST, message) } - pub fn not_found(resource: &str, identifier: S) -> Self { + pub fn not_found(resource: &str, identifier: S, service_version: u16) -> Self { Self::new( StatusCode::NOT_FOUND, format!("{} not found by {}", resource, identifier), ) + .sneedstr_version(service_version) } pub fn invalid_param(name: &str, value: S) -> Self { @@ -76,6 +77,11 @@ impl Error { pub fn status_code(&self) -> StatusCode { StatusCode::from_u16(self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) } + + pub fn sneedstr_version(mut self, service_version: u16) -> Self { + self.sneedstr_version = Some(service_version); + self + } } impl fmt::Display for Error { @@ -110,36 +116,28 @@ mod tests { #[test] fn test_to_string() { let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()); - assert_eq!( - err.to_string(), - "Error { code: 400, message: 'invalid address', sneedstr_version: \"0.1.1\" }" - ) + assert_eq!(err.to_string(), "400 Bad Request: invalid address") } #[test] fn test_from_anyhow_error_as_internal_error() { let err = Error::from(anyhow::format_err!("hello")); - assert_eq!( - err.to_string(), - "Error { code: 500, message: 'hello', sneedstr_version: \"0.1.1\" }" - ) + assert_eq!(err.to_string(), "500 Internal Server Error: hello") } #[test] fn test_to_string_with_sneedstr_version() { - let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()); + let err = + Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()).sneedstr_version(123); assert_eq!( err.to_string(), - "Error { code: 400, message: 'invalid address', sneedstr_version: \"0.1.1\" }" + "400 Bad Request: invalid address\ndiem ledger version: 123" ) } #[test] fn test_internal_error() { let err = Error::internal(anyhow::format_err!("hello")); - assert_eq!( - err.to_string(), - "Error { code: 500, message: 'hello', sneedstr_version: \"0.1.1\" }" - ) + assert_eq!(err.to_string(), "500 Internal Server Error: hello") } } diff --git a/src/utils/structs.rs b/src/utils/structs.rs index 48863d3..e2b91a2 100644 --- a/src/utils/structs.rs +++ b/src/utils/structs.rs @@ -4,7 +4,6 @@ use crate::PubSub; use nostr::{Event, Filter, SubscriptionId}; use std::collections::HashMap; -use std::str::FromStr; use std::sync::Arc; use tokio::sync::mpsc; use uuid::Uuid; @@ -51,8 +50,6 @@ pub struct Client { pub client_id: Uuid, pub client_connection: Option>>, pub subscriptions: HashMap, - pub authenticated: bool, // NIP-42 - challlenge: Option, // NIP-42 max_subs: usize, } @@ -63,8 +60,6 @@ impl Client { client_id: Uuid::new_v4(), client_connection: None, subscriptions: HashMap::new(), - authenticated: false, - challlenge: None, max_subs: MAX_SUBSCRIPTIONS, } } @@ -73,43 +68,6 @@ impl Client { &self.client_ip_addr } - pub fn set_challenge(&mut self, challenge: String) { - self.challlenge = Some(challenge); - } - - pub fn authenticate(&mut self, relay_url: nostr::UncheckedUrl, event: &nostr::Event) { - if self.challlenge.is_none() { - return; - } - - let challenge_tag = nostr::Tag::Challenge(self.challlenge.clone().unwrap()); - let relay_tag = nostr::Tag::Relay(relay_url); - if let Ok(()) = event.verify() { - log::debug!("event is valid"); - if event.kind.as_u32() == 22242 && event.created_at() > nostr::Timestamp::from(nostr::Timestamp::now().as_u64() - 600) { - log::debug!("kind is correct and timestamp is good"); - let mut challenge_matched = false; - let mut relay_matched = false; - event.tags().iter().for_each(|tag| { - if tag == &challenge_tag { - challenge_matched = true; - log::debug!("challenge matched"); - } - - if tag == &relay_tag { - relay_matched = true; - log::debug!("relay matched"); - } - }); - - if challenge_matched && relay_matched { - log::debug!("client now authenticated"); - self.authenticated = true; - } - } - } - } - pub fn subscribe(&mut self, subscription: Subscription) -> Result<(), Error> { let k = subscription.get_id(); let sub_id_len = k.len(); diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..f7ada7e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,66 @@ + + + + + + + Registration + + +
+

Create new user

+
+

Sign Up

+
+ + + +
+
+ + + +
+ +
+
+ + + diff --git a/templates/index.stpl b/templates/index.stpl new file mode 100644 index 0000000..d044eaa --- /dev/null +++ b/templates/index.stpl @@ -0,0 +1,70 @@ + + + + + + + + + + + Registration + + +
+

Create new user

+
+

Sign Up

+
+ + + +
+
+ + + +
+ +
+
+ + + diff --git a/templates/static/android-chrome-192x192.png b/templates/static/android-chrome-192x192.png new file mode 100644 index 0000000..b87bdf7 Binary files /dev/null and b/templates/static/android-chrome-192x192.png differ diff --git a/templates/static/android-chrome-512x512.png b/templates/static/android-chrome-512x512.png new file mode 100644 index 0000000..407a549 Binary files /dev/null and b/templates/static/android-chrome-512x512.png differ diff --git a/templates/static/apple-touch-icon.png b/templates/static/apple-touch-icon.png new file mode 100644 index 0000000..075ba41 Binary files /dev/null and b/templates/static/apple-touch-icon.png differ diff --git a/templates/static/favicon-16x16.png b/templates/static/favicon-16x16.png new file mode 100644 index 0000000..2dd3e01 Binary files /dev/null and b/templates/static/favicon-16x16.png differ diff --git a/templates/static/favicon-32x32.png b/templates/static/favicon-32x32.png new file mode 100644 index 0000000..1027bdd Binary files /dev/null and b/templates/static/favicon-32x32.png differ diff --git a/templates/static/favicon.ico b/templates/static/favicon.ico new file mode 100644 index 0000000..896232f Binary files /dev/null and b/templates/static/favicon.ico differ diff --git a/templates/static/site.webmanifest b/templates/static/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/templates/static/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file