Compare commits
	
		
			No commits in common. "e40a4645a373223cd9550f5937310cc681b49df0" and "255905021c6e488bc8867ef0664d04646e63f847" have entirely different histories.
		
	
	
		
			e40a4645a3
			...
			255905021c
		
	
		
					 35 changed files with 664 additions and 1404 deletions
				
			
		
							
								
								
									
										92
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										92
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -497,6 +497,18 @@ version = "0.1.9"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
 | 
					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]]
 | 
					[[package]]
 | 
				
			||||||
name = "flate2"
 | 
					name = "flate2"
 | 
				
			||||||
version = "1.0.28"
 | 
					version = "1.0.28"
 | 
				
			||||||
| 
						 | 
					@ -749,6 +761,15 @@ version = "0.1.1"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
 | 
					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]]
 | 
					[[package]]
 | 
				
			||||||
name = "http"
 | 
					name = "http"
 | 
				
			||||||
version = "0.2.9"
 | 
					version = "0.2.9"
 | 
				
			||||||
| 
						 | 
					@ -936,6 +957,12 @@ version = "1.0.9"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 | 
					checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "itoap"
 | 
				
			||||||
 | 
					version = "1.0.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "js-sys"
 | 
					name = "js-sys"
 | 
				
			||||||
version = "0.3.64"
 | 
					version = "0.3.64"
 | 
				
			||||||
| 
						 | 
					@ -1564,6 +1591,44 @@ version = "1.0.15"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 | 
					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]]
 | 
					[[package]]
 | 
				
			||||||
name = "scoped-tls"
 | 
					name = "scoped-tls"
 | 
				
			||||||
version = "1.0.1"
 | 
					version = "1.0.1"
 | 
				
			||||||
| 
						 | 
					@ -1676,6 +1741,15 @@ dependencies = [
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "serde_spanned"
 | 
				
			||||||
 | 
					version = "0.6.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "serde_urlencoded"
 | 
					name = "serde_urlencoded"
 | 
				
			||||||
version = "0.7.1"
 | 
					version = "0.7.1"
 | 
				
			||||||
| 
						 | 
					@ -1759,6 +1833,7 @@ dependencies = [
 | 
				
			||||||
 "rusqlite",
 | 
					 "rusqlite",
 | 
				
			||||||
 "rusqlite_migration",
 | 
					 "rusqlite_migration",
 | 
				
			||||||
 "rustls 0.21.6",
 | 
					 "rustls 0.21.6",
 | 
				
			||||||
 | 
					 "sailfish",
 | 
				
			||||||
 "sea-query",
 | 
					 "sea-query",
 | 
				
			||||||
 "sea-query-rusqlite",
 | 
					 "sea-query-rusqlite",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
| 
						 | 
					@ -1987,11 +2062,26 @@ dependencies = [
 | 
				
			||||||
 "tracing",
 | 
					 "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]]
 | 
					[[package]]
 | 
				
			||||||
name = "toml_datetime"
 | 
					name = "toml_datetime"
 | 
				
			||||||
version = "0.6.3"
 | 
					version = "0.6.3"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
 | 
					checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "toml_edit"
 | 
					name = "toml_edit"
 | 
				
			||||||
| 
						 | 
					@ -2000,6 +2090,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
 | 
					checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "indexmap 2.0.0",
 | 
					 "indexmap 2.0.0",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "serde_spanned",
 | 
				
			||||||
 "toml_datetime",
 | 
					 "toml_datetime",
 | 
				
			||||||
 "winnow",
 | 
					 "winnow",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,7 @@ rusqlite_migration = "1.0.2"
 | 
				
			||||||
nostr = "0.27.0"
 | 
					nostr = "0.27.0"
 | 
				
			||||||
nostr-database = "0.27.0"
 | 
					nostr-database = "0.27.0"
 | 
				
			||||||
regex = "1.9.5"
 | 
					regex = "1.9.5"
 | 
				
			||||||
 | 
					sailfish = "0.7.0"
 | 
				
			||||||
sea-query = { version = "0.30.4", features = ["backend-sqlite", "thread-safe"] }
 | 
					sea-query = { version = "0.30.4", features = ["backend-sqlite", "thread-safe"] }
 | 
				
			||||||
sea-query-rusqlite = { version="0", features = [
 | 
					sea-query-rusqlite = { version="0", features = [
 | 
				
			||||||
    "with-chrono",
 | 
					    "with-chrono",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@
 | 
				
			||||||
        naersk' = pkgs.callPackage naersk { };
 | 
					        naersk' = pkgs.callPackage naersk { };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        wwwPath = "www";
 | 
					        wwwPath = "www";
 | 
				
			||||||
 | 
					        templatesPath = "templates";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      in rec {
 | 
					      in rec {
 | 
				
			||||||
        # For `nix build` & `nix run`:
 | 
					        # For `nix build` & `nix run`:
 | 
				
			||||||
| 
						 | 
					@ -24,6 +25,7 @@
 | 
				
			||||||
            mkdir -p $out/templates
 | 
					            mkdir -p $out/templates
 | 
				
			||||||
            mkdir -p $out/www
 | 
					            mkdir -p $out/www
 | 
				
			||||||
            cp -r ${wwwPath} $out/
 | 
					            cp -r ${wwwPath} $out/
 | 
				
			||||||
 | 
					            cp -r ${templatesPath} $out/
 | 
				
			||||||
          '';
 | 
					          '';
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,8 +52,6 @@
 | 
				
			||||||
          env = {
 | 
					          env = {
 | 
				
			||||||
            DATABASE_URL = "/tmp/sqlite.db";
 | 
					            DATABASE_URL = "/tmp/sqlite.db";
 | 
				
			||||||
            ADMIN_PUBKEY = "npub1m2w0ckmkgj4wtvl8muwjynh56j3qd4nddca4exdg4mdrkepvfnhsmusy54";
 | 
					            ADMIN_PUBKEY = "npub1m2w0ckmkgj4wtvl8muwjynh56j3qd4nddca4exdg4mdrkepvfnhsmusy54";
 | 
				
			||||||
            CONFIG_ENABLE_AUTH = "false";
 | 
					 | 
				
			||||||
            CONFIG_RELAY_URL = "ws://0.0.0.0:8080";
 | 
					 | 
				
			||||||
            RUST_BACKTRACE = 1;
 | 
					            RUST_BACKTRACE = 1;
 | 
				
			||||||
            RUST_LOG = "debug";
 | 
					            RUST_LOG = "debug";
 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,11 +21,6 @@ in {
 | 
				
			||||||
      'npub' of the administrator account. Must be defined!
 | 
					      '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";
 | 
					    sslEnable = mkEnableOption "Whether to enable ACME SSL for nginx proxy";
 | 
				
			||||||
    hostAddress = mkOption {
 | 
					    hostAddress = mkOption {
 | 
				
			||||||
      type = types.nullOr types.str;
 | 
					      type = types.nullOr types.str;
 | 
				
			||||||
| 
						 | 
					@ -41,10 +36,6 @@ in {
 | 
				
			||||||
        Local nixos-container ip address
 | 
					        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 {
 | 
					  config = mkIf cfg.enable {
 | 
				
			||||||
| 
						 | 
					@ -73,8 +64,6 @@ in {
 | 
				
			||||||
          environment = {
 | 
					          environment = {
 | 
				
			||||||
            DATABASE_URL = "${DB_PATH}/sneedstr.db";
 | 
					            DATABASE_URL = "${DB_PATH}/sneedstr.db";
 | 
				
			||||||
            ADMIN_PUBKEY = cfg.adminPubkey;
 | 
					            ADMIN_PUBKEY = cfg.adminPubkey;
 | 
				
			||||||
            CONFIG_ENABLE_AUTH = boolToString cfg.enableAuth;
 | 
					 | 
				
			||||||
            CONFIG_RELAY_URL = cfg.relayUrl;
 | 
					 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
          startLimitBurst = 1;
 | 
					          startLimitBurst = 1;
 | 
				
			||||||
          startLimitIntervalSec = 10;
 | 
					          startLimitIntervalSec = 10;
 | 
				
			||||||
| 
						 | 
					@ -122,10 +111,6 @@ in {
 | 
				
			||||||
          proxyWebsockets = true; # needed if you need to use WebSocket
 | 
					          proxyWebsockets = true; # needed if you need to use WebSocket
 | 
				
			||||||
          recommendedProxySettings = true;
 | 
					          recommendedProxySettings = true;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        locations."/register" = {
 | 
					 | 
				
			||||||
          proxyPass = "http://${cfg.localAddress}:8085";
 | 
					 | 
				
			||||||
          recommendedProxySettings = true;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,15 @@
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    noose::sled::BanInfo,
 | 
					    noose::sled::BanInfo,
 | 
				
			||||||
    noose::user::{User, UserRow, Nip05Profile},
 | 
					    noose::user::{User, UserRow},
 | 
				
			||||||
    utils::{error::Error, structs::Subscription}, usernames::dto::UserBody,
 | 
					    utils::{error::Error, structs::Subscription},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use nostr::secp256k1::XOnlyPublicKey;
 | 
					use nostr::secp256k1::XOnlyPublicKey;
 | 
				
			||||||
use std::{collections::{HashMap, BTreeSet}, fmt::Debug};
 | 
					use std::{collections::HashMap, fmt::Debug};
 | 
				
			||||||
use tokio::sync::{broadcast, Mutex};
 | 
					use tokio::sync::{broadcast, Mutex};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod channels {
 | 
					pub mod channels {
 | 
				
			||||||
    pub static MSG_NOOSE: &str = "MSG_NOOSE";
 | 
					    pub static MSG_NOOSE: &str = "MSG_NOOSE";
 | 
				
			||||||
    pub static MSG_NIP05: &str = "MSG_NIP05";
 | 
					    pub static MSG_NIP05: &str = "MSG_NIP05";
 | 
				
			||||||
    pub static MSG_AUTH: &str = "MSG_AUTH";
 | 
					 | 
				
			||||||
    pub static MSG_RELAY: &str = "MSG_RELAY";
 | 
					    pub static MSG_RELAY: &str = "MSG_RELAY";
 | 
				
			||||||
    pub static MSG_PIPELINE: &str = "MSG_PIPELINE";
 | 
					    pub static MSG_PIPELINE: &str = "MSG_PIPELINE";
 | 
				
			||||||
    pub static MSG_SLED: &str = "MSG_SLED";
 | 
					    pub static MSG_SLED: &str = "MSG_SLED";
 | 
				
			||||||
| 
						 | 
					@ -19,21 +18,18 @@ pub mod channels {
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq)]
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
pub enum Command {
 | 
					pub enum Command {
 | 
				
			||||||
    // DbRequest
 | 
					    // DbRequest
 | 
				
			||||||
    // --- Req
 | 
					 | 
				
			||||||
    DbReqWriteEvent(/* client_id */ uuid::Uuid, Box<nostr::Event>),
 | 
					    DbReqWriteEvent(/* client_id */ uuid::Uuid, Box<nostr::Event>),
 | 
				
			||||||
    DbReqFindEvent(/* client_id*/ uuid::Uuid, Subscription),
 | 
					    DbReqFindEvent(/* client_id*/ uuid::Uuid, Subscription),
 | 
				
			||||||
    DbReqDeleteEvents(/* client_id*/ uuid::Uuid, Box<nostr::Event>),
 | 
					    DbReqDeleteEvents(/* client_id*/ uuid::Uuid, Box<nostr::Event>),
 | 
				
			||||||
    DbReqEventCounts(/* client_id*/ uuid::Uuid, Subscription),
 | 
					    DbReqEventCounts(/* client_id*/ uuid::Uuid, Subscription),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Old messages
 | 
					    // Old messages
 | 
				
			||||||
    DbReqInsertUser(UserRow),
 | 
					    DbReqInsertUser(UserRow),
 | 
				
			||||||
 | 
					    DbReqGetUser(User),
 | 
				
			||||||
    DbReqCreateAccount(XOnlyPublicKey, String, String),
 | 
					    DbReqCreateAccount(XOnlyPublicKey, String, String),
 | 
				
			||||||
    DbReqGetAccount(String),
 | 
					    DbReqGetAccount(String),
 | 
				
			||||||
    DbReqClear,
 | 
					    DbReqClear,
 | 
				
			||||||
    // NIP-05 related messages
 | 
					    // DbResponse
 | 
				
			||||||
    DbReqGetNIP05(String),
 | 
					 | 
				
			||||||
    DbReqCreateNIP05(UserBody),
 | 
					 | 
				
			||||||
    // --- Res
 | 
					 | 
				
			||||||
    DbResNIP05(Nip05Profile),
 | 
					 | 
				
			||||||
    DbResRelayMessages(
 | 
					    DbResRelayMessages(
 | 
				
			||||||
        /* client_id*/ uuid::Uuid,
 | 
					        /* client_id*/ uuid::Uuid,
 | 
				
			||||||
        /* Vec<RelayMessage::Event> */ Vec<nostr::RelayMessage>,
 | 
					        /* Vec<RelayMessage::Event> */ Vec<nostr::RelayMessage>,
 | 
				
			||||||
| 
						 | 
					@ -42,48 +38,26 @@ pub enum Command {
 | 
				
			||||||
    DbResOk,
 | 
					    DbResOk,
 | 
				
			||||||
    DbResOkWithStatus(/* client_id */ uuid::Uuid, nostr::RelayMessage),
 | 
					    DbResOkWithStatus(/* client_id */ uuid::Uuid, nostr::RelayMessage),
 | 
				
			||||||
    DbResAccount, // TODO: Add Account DTO as a param
 | 
					    DbResAccount, // TODO: Add Account DTO as a param
 | 
				
			||||||
 | 
					    DbResUser(UserRow),
 | 
				
			||||||
    DbResEventCounts(/* client_id */ uuid::Uuid, nostr::RelayMessage),
 | 
					    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<XOnlyPublicKey>),
 | 
					 | 
				
			||||||
    DbResGetContacts(BTreeSet<nostr_database::Profile>),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Event Pipeline
 | 
					    // Event Pipeline
 | 
				
			||||||
    // --- Req
 | 
					 | 
				
			||||||
    PipelineReqEvent(/* client_id */ uuid::Uuid, Box<nostr::Event>),
 | 
					    PipelineReqEvent(/* client_id */ uuid::Uuid, Box<nostr::Event>),
 | 
				
			||||||
    // --- Res
 | 
					 | 
				
			||||||
    PipelineResRelayMessageOk(/* client_id */ uuid::Uuid, nostr::RelayMessage),
 | 
					    PipelineResRelayMessageOk(/* client_id */ uuid::Uuid, nostr::RelayMessage),
 | 
				
			||||||
    PipelineResStreamOutEvent(Box<nostr::Event>),
 | 
					    PipelineResStreamOutEvent(Box<nostr::Event>),
 | 
				
			||||||
    PipelineResOk,
 | 
					    PipelineResOk,
 | 
				
			||||||
    
 | 
					    // Subscription Errors
 | 
				
			||||||
 | 
					    ClientSubscriptionError(/* error message */ String),
 | 
				
			||||||
    // Sled
 | 
					    // Sled
 | 
				
			||||||
    // --- Req
 | 
					 | 
				
			||||||
    SledReqBanUser(Box<BanInfo>),
 | 
					    SledReqBanUser(Box<BanInfo>),
 | 
				
			||||||
    SledReqBanInfo(/* pubkey */ String),
 | 
					    SledReqBanInfo(/* pubkey */ String),
 | 
				
			||||||
    SledReqUnbanUser(/* pubkey */ String),
 | 
					    SledReqUnbanUser(/* pubkey */ String),
 | 
				
			||||||
    SledReqGetBans,
 | 
					    SledReqGetBans,
 | 
				
			||||||
    // --- Res
 | 
					 | 
				
			||||||
    SledResBan(Option<BanInfo>),
 | 
					    SledResBan(Option<BanInfo>),
 | 
				
			||||||
    SledResBans(Vec<BanInfo>),
 | 
					    SledResBans(Vec<BanInfo>),
 | 
				
			||||||
    SledResSuccess(bool),
 | 
					    SledResSuccess(bool),
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Other
 | 
					    // Other
 | 
				
			||||||
    ServiceRegistrationRequired(/* client_id */ uuid::Uuid, nostr::RelayMessage),
 | 
					 | 
				
			||||||
    Str(String),
 | 
					    Str(String),
 | 
				
			||||||
    ServiceError(Error),
 | 
					    ServiceError(Error),
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Subscription Errors
 | 
					 | 
				
			||||||
    ClientSubscriptionError(/* error message */ String),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // --- Noop
 | 
					 | 
				
			||||||
    Noop,
 | 
					    Noop,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -112,7 +86,7 @@ impl PubSub {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn subscribe(&self, topic: &str) -> broadcast::Receiver<Message> {
 | 
					    pub async fn subscribe(&self, topic: &str) -> broadcast::Receiver<Message> {
 | 
				
			||||||
        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;
 | 
					        let mut subscribers = self.subscribers.lock().await;
 | 
				
			||||||
        subscribers
 | 
					        subscribers
 | 
				
			||||||
            .entry(topic.to_string())
 | 
					            .entry(topic.to_string())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,58 +1,17 @@
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    bussy::PubSub,
 | 
					    bussy::PubSub,
 | 
				
			||||||
    usernames::dto::UserBody,
 | 
					 | 
				
			||||||
    utils::{error::Error, structs::Subscription},
 | 
					    utils::{error::Error, structs::Subscription},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use nostr::{secp256k1::XOnlyPublicKey, Event, RelayMessage};
 | 
					use nostr::{Event, RelayMessage};
 | 
				
			||||||
use nostr_database::Profile;
 | 
					use std::sync::Arc;
 | 
				
			||||||
use std::{collections::BTreeSet, sync::Arc};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::user::Nip05Profile;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Handle core nostr events
 | 
					 | 
				
			||||||
pub trait Noose: Send + Sync {
 | 
					pub trait Noose: Send + Sync {
 | 
				
			||||||
    /// Start event listener
 | 
					 | 
				
			||||||
    async fn start(&mut self, pubsub: Arc<PubSub>) -> Result<(), Error>;
 | 
					    async fn start(&mut self, pubsub: Arc<PubSub>) -> Result<(), Error>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Save event in the Database
 | 
					 | 
				
			||||||
    async fn write_event(&self, event: Box<Event>) -> Result<RelayMessage, Error>;
 | 
					    async fn write_event(&self, event: Box<Event>) -> Result<RelayMessage, Error>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Find events by subscription
 | 
					 | 
				
			||||||
    async fn find_event(&self, subscription: Subscription) -> Result<Vec<RelayMessage>, Error>;
 | 
					    async fn find_event(&self, subscription: Subscription) -> Result<Vec<RelayMessage>, Error>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get event counts by subscription
 | 
					 | 
				
			||||||
    async fn counts(&self, subscription: Subscription) -> Result<RelayMessage, Error>;
 | 
					    async fn counts(&self, subscription: Subscription) -> Result<RelayMessage, Error>;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Get NIP-05 of the registered User by 'username'
 | 
					 | 
				
			||||||
    async fn get_nip05(&self, username: String) -> Result<Nip05Profile, Error>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// 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<Profile, Error>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// 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<Vec<XOnlyPublicKey>, Error>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Get Profie contacts list
 | 
					 | 
				
			||||||
    async fn contacts(&self, public_key: XOnlyPublicKey) -> Result<BTreeSet<Profile>, Error>;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					@ -11,8 +11,7 @@ impl MigrationRunner {
 | 
				
			||||||
        let m_events_fts = include_str!("./1697410223576_events_fts.sql");
 | 
					        let m_events_fts = include_str!("./1697410223576_events_fts.sql");
 | 
				
			||||||
        let m_users = include_str!("./1697410294265_users.sql");
 | 
					        let m_users = include_str!("./1697410294265_users.sql");
 | 
				
			||||||
        let m_unattached_media = include_str!("./1697410480767_unattached_media.sql");
 | 
					        let m_unattached_media = include_str!("./1697410480767_unattached_media.sql");
 | 
				
			||||||
        let m_nip05 = include_str!("./1706575155557_nip05.sql");
 | 
					        let m_pragma = include_str!("./1697410424624_pragma.sql");
 | 
				
			||||||
        let m_nip42 = include_str!("./1707327016995_nip42_profile.sql");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let migrations = Migrations::new(vec![
 | 
					        let migrations = Migrations::new(vec![
 | 
				
			||||||
            M::up(m_create_events),
 | 
					            M::up(m_create_events),
 | 
				
			||||||
| 
						 | 
					@ -21,8 +20,7 @@ impl MigrationRunner {
 | 
				
			||||||
            M::up(m_events_fts),
 | 
					            M::up(m_events_fts),
 | 
				
			||||||
            M::up(m_users),
 | 
					            M::up(m_users),
 | 
				
			||||||
            M::up(m_unattached_media),
 | 
					            M::up(m_unattached_media),
 | 
				
			||||||
            M::up(m_nip05),
 | 
					            M::up(m_pragma),
 | 
				
			||||||
            M::up(m_nip42),
 | 
					 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match migrations.to_latest(connection) {
 | 
					        match migrations.to_latest(connection) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,6 @@ mod migrations;
 | 
				
			||||||
pub mod pipeline;
 | 
					pub mod pipeline;
 | 
				
			||||||
pub mod sled;
 | 
					pub mod sled;
 | 
				
			||||||
mod sqlite;
 | 
					mod sqlite;
 | 
				
			||||||
mod sqlite_tables;
 | 
					 | 
				
			||||||
pub mod user;
 | 
					pub mod user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn start(context: Context) {
 | 
					pub fn start(context: Context) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@ impl Pipeline {
 | 
				
			||||||
            let channel;
 | 
					            let channel;
 | 
				
			||||||
            let command = match message.content {
 | 
					            let command = match message.content {
 | 
				
			||||||
                Command::PipelineReqEvent(client_id, event) => {
 | 
					                Command::PipelineReqEvent(client_id, event) => {
 | 
				
			||||||
                    match self.handle_event(client_id, event).await {
 | 
					                    match self.handle_event(client_id, event.clone()).await {
 | 
				
			||||||
                        Ok(_) => {
 | 
					                        Ok(_) => {
 | 
				
			||||||
                            log::info!("[Pipeline] handle_event completed");
 | 
					                            log::info!("[Pipeline] handle_event completed");
 | 
				
			||||||
                            channel = message.source;
 | 
					                            channel = message.source;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,27 +1,197 @@
 | 
				
			||||||
use super::{
 | 
					use super::{db::Noose, migrations::MigrationRunner};
 | 
				
			||||||
    db::Noose,
 | 
					 | 
				
			||||||
    migrations::MigrationRunner,
 | 
					 | 
				
			||||||
    user::{Nip05Profile, Nip05Table, UserRow},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    bussy::{channels, Command, Message, PubSub},
 | 
					    bussy::{channels, Command, Message, PubSub},
 | 
				
			||||||
    usernames::dto::UserBody,
 | 
					 | 
				
			||||||
    utils::{config::Config as ServiceConfig, error::Error, structs::Subscription},
 | 
					    utils::{config::Config as ServiceConfig, error::Error, structs::Subscription},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use async_trait::async_trait;
 | 
					use async_trait::async_trait;
 | 
				
			||||||
use deadpool_sqlite::{Config, Object, Pool, Runtime};
 | 
					use deadpool_sqlite::{Config, Object, Pool, Runtime};
 | 
				
			||||||
use nostr::{
 | 
					use nostr::{
 | 
				
			||||||
    nips::nip01::Coordinate, secp256k1::XOnlyPublicKey, Event, EventId, Filter, JsonUtil,
 | 
					    nips::nip01::Coordinate, Event, EventId, Filter, RelayMessage, TagKind, Timestamp, Url,
 | 
				
			||||||
    RelayMessage, TagKind, Timestamp, Url,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use nostr_database::{Backend, DatabaseOptions, NostrDatabase, Order};
 | 
					use nostr_database::{Backend, DatabaseOptions, NostrDatabase, Order};
 | 
				
			||||||
 | 
					use rusqlite::Row;
 | 
				
			||||||
use sea_query::{extension::sqlite::SqliteExpr, Order as SqOrder, Query, SqliteQueryBuilder};
 | 
					use sea_query::{extension::sqlite::SqliteExpr, Order as SqOrder, Query, SqliteQueryBuilder};
 | 
				
			||||||
use sea_query_rusqlite::RusqliteBinder;
 | 
					use sea_query_rusqlite::RusqliteBinder;
 | 
				
			||||||
use std::sync::Arc;
 | 
					use std::sync::Arc;
 | 
				
			||||||
use std::{collections::HashSet, str::FromStr};
 | 
					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<Vec<String>> = 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)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct NostrSqlite {
 | 
					pub struct NostrSqlite {
 | 
				
			||||||
| 
						 | 
					@ -42,22 +212,7 @@ impl NostrSqlite {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn run_migrations(pool: &Pool) -> bool {
 | 
					    async fn run_migrations(pool: &Pool) -> bool {
 | 
				
			||||||
        let connection = pool.get().await.unwrap();
 | 
					        let connection = pool.get().await.unwrap();
 | 
				
			||||||
        connection.interact(MigrationRunner::up).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
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn get_connection(&self) -> Result<Object, Error> {
 | 
					    async fn get_connection(&self) -> Result<Object, Error> {
 | 
				
			||||||
| 
						 | 
					@ -341,159 +496,86 @@ impl NostrSqlite {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        return Ok(false);
 | 
					                        return Ok(false);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
                    match tx.commit() {
 | 
					                    log::debug!("inserting new event in events");
 | 
				
			||||||
                        Ok(_) => return Ok(true),
 | 
					                    // Insert into Events table
 | 
				
			||||||
                        Err(err) => {
 | 
					                    let (sql, values) = Query::insert()
 | 
				
			||||||
                            log::error!("Error during transaction commit: {}", err);
 | 
					                        .into_table(EventsTable::Table)
 | 
				
			||||||
                            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)
 | 
					 | 
				
			||||||
                        .columns([
 | 
					                        .columns([
 | 
				
			||||||
                            MetadataTable::Pubkey,
 | 
					                            EventsTable::EventId,
 | 
				
			||||||
                            MetadataTable::Name,
 | 
					                            EventsTable::Content,
 | 
				
			||||||
                            MetadataTable::DisplayName,
 | 
					                            EventsTable::Kind,
 | 
				
			||||||
                            MetadataTable::About,
 | 
					                            EventsTable::Pubkey,
 | 
				
			||||||
                            MetadataTable::Website,
 | 
					                            EventsTable::CreatedAt,
 | 
				
			||||||
                            MetadataTable::Picture,
 | 
					                            EventsTable::Tags,
 | 
				
			||||||
                            MetadataTable::Banner,
 | 
					                            EventsTable::Sig,
 | 
				
			||||||
                            MetadataTable::Nip05,
 | 
					 | 
				
			||||||
                            MetadataTable::Lud06,
 | 
					 | 
				
			||||||
                            MetadataTable::Lud16,
 | 
					 | 
				
			||||||
                            MetadataTable::Custom,
 | 
					 | 
				
			||||||
                        ])
 | 
					                        ])
 | 
				
			||||||
                        .values_panic([
 | 
					                        .values_panic([
 | 
				
			||||||
                            pubkey.clone().into(),
 | 
					                            id.clone().into(),
 | 
				
			||||||
                            metadata.name.unwrap_or_default().into(),
 | 
					                            content.clone().into(),
 | 
				
			||||||
                            metadata.display_name.unwrap_or_default().into(),
 | 
					                            kind.into(),
 | 
				
			||||||
                            metadata.about.unwrap_or_default().into(),
 | 
					                            pubkey.into(),
 | 
				
			||||||
                            metadata.website.unwrap_or_default().into(),
 | 
					                            created_at.into(),
 | 
				
			||||||
                            metadata.picture.unwrap_or_default().into(),
 | 
					                            tags.into(),
 | 
				
			||||||
                            metadata.banner.unwrap_or_default().into(),
 | 
					                            sig.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(),
 | 
					 | 
				
			||||||
                        ])
 | 
					                        ])
 | 
				
			||||||
                        .build_rusqlite(SqliteQueryBuilder);
 | 
					                        .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();
 | 
					                        tx.rollback().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        log::debug!("Failed to store Metadata: {}", err);
 | 
					 | 
				
			||||||
                        return Ok(false);
 | 
					                        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()) {
 | 
					                    // Insert into Tags table
 | 
				
			||||||
                    log::error!("Error inserting event into 'events' table: {}", err);
 | 
					                    log::debug!("inserting new event into tags");
 | 
				
			||||||
                    tx.rollback().unwrap();
 | 
					                    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
 | 
					                                        return Ok(false);
 | 
				
			||||||
                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);
 | 
					 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
| 
						 | 
					@ -762,12 +844,6 @@ impl NostrSqlite {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let event = row.clone().to_event();
 | 
					                let event = row.clone().to_event();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if event.is_expired() {
 | 
					 | 
				
			||||||
                    return Err(Error::internal_with_message(
 | 
					 | 
				
			||||||
                        "Event has expired. Ignoring...",
 | 
					 | 
				
			||||||
                    ));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Ok(event)
 | 
					                Ok(event)
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
| 
						 | 
					@ -983,9 +1059,7 @@ impl NostrSqlite {
 | 
				
			||||||
                let mut event_vec: Vec<Event> = vec![];
 | 
					                let mut event_vec: Vec<Event> = vec![];
 | 
				
			||||||
                while let Ok(Some(row)) = rows.next() {
 | 
					                while let Ok(Some(row)) = rows.next() {
 | 
				
			||||||
                    let event = EventRow::from(row).to_event();
 | 
					                    let event = EventRow::from(row).to_event();
 | 
				
			||||||
                    if !event.is_expired() {
 | 
					                    event_vec.push(event);
 | 
				
			||||||
                        event_vec.push(event);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Ok(event_vec)
 | 
					                Ok(event_vec)
 | 
				
			||||||
| 
						 | 
					@ -1060,6 +1134,8 @@ impl NostrSqlite {
 | 
				
			||||||
        let Ok(query_result) = connection
 | 
					        let Ok(query_result) = connection
 | 
				
			||||||
            .interact(move |conn| {
 | 
					            .interact(move |conn| {
 | 
				
			||||||
                let (sql, values) = sql_statement
 | 
					                let (sql, values) = sql_statement
 | 
				
			||||||
 | 
					                    .clear_selects()
 | 
				
			||||||
 | 
					                    .column(EventsTable::EventId)
 | 
				
			||||||
                    .order_by(EventsTable::CreatedAt, sq_order.to_owned())
 | 
					                    .order_by(EventsTable::CreatedAt, sq_order.to_owned())
 | 
				
			||||||
                    .build_rusqlite(SqliteQueryBuilder);
 | 
					                    .build_rusqlite(SqliteQueryBuilder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1068,10 +1144,9 @@ impl NostrSqlite {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let mut event_vec: Vec<EventId> = vec![];
 | 
					                let mut event_vec: Vec<EventId> = vec![];
 | 
				
			||||||
                while let Ok(Some(row)) = rows.next() {
 | 
					                while let Ok(Some(row)) = rows.next() {
 | 
				
			||||||
                    let event = EventRow::from(row).to_event();
 | 
					                    let event_id_string: String = row.get(0).unwrap();
 | 
				
			||||||
                    if !event.is_expired() {
 | 
					                    let event_id = EventId::from_str(&event_id_string).unwrap();
 | 
				
			||||||
                        event_vec.push(event.id);
 | 
					                    event_vec.push(event_id);
 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Ok(event_vec)
 | 
					                Ok(event_vec)
 | 
				
			||||||
| 
						 | 
					@ -1103,37 +1178,33 @@ impl NostrSqlite {
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let Ok(query_result) = connection
 | 
					        let Ok(query_result) = connection
 | 
				
			||||||
            .interact(
 | 
					            .interact(move |conn: &mut rusqlite::Connection| -> Result<bool, Error> {
 | 
				
			||||||
                move |conn: &mut rusqlite::Connection| -> Result<bool, Error> {
 | 
					                let (sql, value) = Query::select()
 | 
				
			||||||
                    let (sql, value) = Query::select()
 | 
					                    .from(EventsTable::Table)
 | 
				
			||||||
                        .from(EventsTable::Table)
 | 
					                    .columns([EventsTable::EventId, EventsTable::CreatedAt])
 | 
				
			||||||
                        .columns([EventsTable::EventId, EventsTable::CreatedAt])
 | 
					                    .left_join(
 | 
				
			||||||
                        .left_join(
 | 
					                        TagsTable::Table,
 | 
				
			||||||
                            TagsTable::Table,
 | 
					                        sea_query::Expr::col((TagsTable::Table, TagsTable::EventId))
 | 
				
			||||||
                            sea_query::Expr::col((TagsTable::Table, TagsTable::EventId))
 | 
					                            .equals((EventsTable::Table, EventsTable::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::Tag)).eq("a"))
 | 
					                    .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Value)).eq(ident))
 | 
				
			||||||
                        .and_where(
 | 
					                    .and_where(
 | 
				
			||||||
                            sea_query::Expr::col((TagsTable::Table, TagsTable::Value)).eq(ident),
 | 
					                        sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt))
 | 
				
			||||||
                        )
 | 
					                            .gte(timestamp.as_i64()),
 | 
				
			||||||
                        .and_where(
 | 
					                    )
 | 
				
			||||||
                            sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt))
 | 
					                    .limit(1)
 | 
				
			||||||
                                .gte(timestamp.as_i64()),
 | 
					                    .build_rusqlite(SqliteQueryBuilder);
 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                        .limit(1)
 | 
					 | 
				
			||||||
                        .build_rusqlite(SqliteQueryBuilder);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    let mut stmt = conn.prepare(sql.as_str()).unwrap();
 | 
					                let mut stmt = conn.prepare(sql.as_str()).unwrap();
 | 
				
			||||||
                    let mut rows = stmt.query(&*value.as_params()).unwrap();
 | 
					                let mut rows = stmt.query(&*value.as_params()).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if let Ok(Some(record)) = rows.next() {
 | 
					                if let Ok(Some(record)) = rows.next() {
 | 
				
			||||||
                        return Ok(false);
 | 
					                    return Ok(false)
 | 
				
			||||||
                    }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    Ok(true)
 | 
					                Ok(true)
 | 
				
			||||||
                },
 | 
					            })
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            return Err(Error::internal_with_message(
 | 
					            return Err(Error::internal_with_message(
 | 
				
			||||||
| 
						 | 
					@ -1143,147 +1214,6 @@ impl NostrSqlite {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        query_result
 | 
					        query_result
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn get_nip05_profile(&self, username: String) -> Result<Nip05Profile, 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<Nip05Profile, Error> {
 | 
					 | 
				
			||||||
                    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<nostr_database::Profile, Error> {
 | 
					 | 
				
			||||||
        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<nostr_database::Profile, Error> {
 | 
					 | 
				
			||||||
                    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<nostr_database::DatabaseError> for Error {
 | 
					impl From<nostr_database::DatabaseError> for Error {
 | 
				
			||||||
| 
						 | 
					@ -1342,8 +1272,7 @@ impl NostrDatabase for NostrSqlite {
 | 
				
			||||||
        coordinate: &Coordinate,
 | 
					        coordinate: &Coordinate,
 | 
				
			||||||
        timestamp: Timestamp,
 | 
					        timestamp: Timestamp,
 | 
				
			||||||
    ) -> Result<bool, Self::Err> {
 | 
					    ) -> Result<bool, Self::Err> {
 | 
				
			||||||
        self.has_coordinate_been_deleted(coordinate, timestamp)
 | 
					        self.has_coordinate_been_deleted(coordinate, timestamp).await
 | 
				
			||||||
            .await
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Set [`EventId`] as seen by relay
 | 
					    /// Set [`EventId`] as seen by relay
 | 
				
			||||||
| 
						 | 
					@ -1410,7 +1339,6 @@ impl Noose for NostrSqlite {
 | 
				
			||||||
        while let Ok(message) = subscriber.recv().await {
 | 
					        while let Ok(message) = subscriber.recv().await {
 | 
				
			||||||
            log::info!("[Noose] received message: {:?}", message);
 | 
					            log::info!("[Noose] received message: {:?}", message);
 | 
				
			||||||
            let command = match message.content {
 | 
					            let command = match message.content {
 | 
				
			||||||
                // Relay Events
 | 
					 | 
				
			||||||
                Command::DbReqWriteEvent(client_id, event) => match self.write_event(event).await {
 | 
					                Command::DbReqWriteEvent(client_id, event) => match self.write_event(event).await {
 | 
				
			||||||
                    Ok(status) => Command::DbResOkWithStatus(client_id, status),
 | 
					                    Ok(status) => Command::DbResOkWithStatus(client_id, status),
 | 
				
			||||||
                    Err(e) => Command::ServiceError(e),
 | 
					                    Err(e) => Command::ServiceError(e),
 | 
				
			||||||
| 
						 | 
					@ -1435,44 +1363,6 @@ impl Noose for NostrSqlite {
 | 
				
			||||||
                        Err(e) => Command::ServiceError(e),
 | 
					                        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,
 | 
					                _ => Command::Noop,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            if command != Command::Noop {
 | 
					            if command != Command::Noop {
 | 
				
			||||||
| 
						 | 
					@ -1496,6 +1386,7 @@ impl Noose for NostrSqlite {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn write_event(&self, event: Box<nostr::Event>) -> Result<nostr::RelayMessage, Error> {
 | 
					    async fn write_event(&self, event: Box<nostr::Event>) -> Result<nostr::RelayMessage, Error> {
 | 
				
			||||||
 | 
					        // TODO: Maybe do event validation and admin deletions here
 | 
				
			||||||
        match self.save_event(&event).await {
 | 
					        match self.save_event(&event).await {
 | 
				
			||||||
            Ok(status) => {
 | 
					            Ok(status) => {
 | 
				
			||||||
                let relay_message = nostr::RelayMessage::ok(event.id, 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())),
 | 
					            Err(err) => Err(Error::internal_with_message(err.to_string())),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn get_nip05(&self, username: String) -> Result<Nip05Profile, Error> {
 | 
					 | 
				
			||||||
        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<nostr_database::Profile, Error> {
 | 
					 | 
				
			||||||
        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<Vec<nostr::prelude::XOnlyPublicKey>, Error> {
 | 
					 | 
				
			||||||
        todo!()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn contacts(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        public_key: nostr::prelude::XOnlyPublicKey,
 | 
					 | 
				
			||||||
    ) -> Result<std::collections::BTreeSet<nostr_database::Profile>, Error> {
 | 
					 | 
				
			||||||
        todo!()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use crate::noose::sqlite::*;
 | 
					    use crate::noose::sqlite::*;
 | 
				
			||||||
    use nostr::key::FromSkStr;
 | 
					 | 
				
			||||||
    use nostr::EventBuilder;
 | 
					    use nostr::EventBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[tokio::test]
 | 
					    #[tokio::test]
 | 
				
			||||||
| 
						 | 
					@ -1811,29 +1656,6 @@ mod tests {
 | 
				
			||||||
        assert_eq!(result.len(), 1)
 | 
					        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]
 | 
					    #[tokio::test]
 | 
				
			||||||
    async fn save_event_with_a_tag() {
 | 
					    async fn save_event_with_a_tag() {
 | 
				
			||||||
        let config = Arc::new(ServiceConfig::new());
 | 
					        let config = Arc::new(ServiceConfig::new());
 | 
				
			||||||
| 
						 | 
					@ -1874,14 +1696,4 @@ mod tests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dbg!(res);
 | 
					        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());
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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<Vec<String>> = 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<String>,
 | 
					 | 
				
			||||||
    display_name: Option<String>,
 | 
					 | 
				
			||||||
    about: Option<String>,
 | 
					 | 
				
			||||||
    website: Option<String>,
 | 
					 | 
				
			||||||
    picture: Option<String>,
 | 
					 | 
				
			||||||
    banner: Option<String>,
 | 
					 | 
				
			||||||
    nip05: Option<String>,
 | 
					 | 
				
			||||||
    lud06: Option<String>,
 | 
					 | 
				
			||||||
    lud16: Option<String>,
 | 
					 | 
				
			||||||
    custom: Option<String>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<&Row<'_>> for ProfileRow {
 | 
					 | 
				
			||||||
    fn from(row: &Row) -> Self {
 | 
					 | 
				
			||||||
        let pubkey: String = row.get("pubkey").unwrap();
 | 
					 | 
				
			||||||
        let name: Option<String> = row.get("name").unwrap_or_default();
 | 
					 | 
				
			||||||
        let display_name: Option<String> = row.get("display_name").unwrap_or_default();
 | 
					 | 
				
			||||||
        let about = row.get("about").unwrap_or_default();
 | 
					 | 
				
			||||||
        let website: Option<String> = row.get("website").unwrap_or_default();
 | 
					 | 
				
			||||||
        let picture: Option<String> = row.get("picture").unwrap_or_default();
 | 
					 | 
				
			||||||
        let banner: Option<String> = row.get("banner").unwrap_or_default();
 | 
					 | 
				
			||||||
        let nip05: Option<String> = row.get("nip05").unwrap_or_default();
 | 
					 | 
				
			||||||
        let lud06: Option<String> = row.get("lud06").unwrap_or_default();
 | 
					 | 
				
			||||||
        let lud16: Option<String> = row.get("lud16").unwrap_or_default();
 | 
					 | 
				
			||||||
        let custom: Option<String> = 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<String, serde_json::Value> = match row.custom {
 | 
					 | 
				
			||||||
            Some(fields_str) => {
 | 
					 | 
				
			||||||
                let fields: HashMap<String, serde_json::Value> =
 | 
					 | 
				
			||||||
                    serde_json::from_str(&fields_str).unwrap_or(HashMap::new());
 | 
					 | 
				
			||||||
                fields
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            None => {
 | 
					 | 
				
			||||||
                let f: HashMap<String, serde_json::Value> = 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)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,103 +1,27 @@
 | 
				
			||||||
 | 
					use chrono::Utc;
 | 
				
			||||||
use regex::Regex;
 | 
					use regex::Regex;
 | 
				
			||||||
use rusqlite::Row;
 | 
					 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use std::collections::HashMap;
 | 
					 | 
				
			||||||
use validator::{Validate, ValidationError};
 | 
					use validator::{Validate, ValidationError};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lazy_static! {
 | 
					lazy_static! {
 | 
				
			||||||
    static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap();
 | 
					    static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub enum Nip05Table {
 | 
					#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
 | 
				
			||||||
    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)]
 | 
					 | 
				
			||||||
pub struct UserRow {
 | 
					pub struct UserRow {
 | 
				
			||||||
    pub pubkey: String,
 | 
					    pub pubkey: String,
 | 
				
			||||||
    pub username: String,
 | 
					    pub username: String,
 | 
				
			||||||
    relays: Vec<String>,
 | 
					    inserted_at: i64,
 | 
				
			||||||
    joined_at: i64,
 | 
					    admin: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl UserRow {
 | 
					impl UserRow {
 | 
				
			||||||
    pub fn new(pubkey: String, username: String, relays: Vec<String>) -> Self {
 | 
					    pub fn new(pubkey: String, username: String, admin: bool) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            pubkey,
 | 
					            pubkey,
 | 
				
			||||||
            username,
 | 
					            username,
 | 
				
			||||||
            relays,
 | 
					            inserted_at: Utc::now().timestamp(),
 | 
				
			||||||
            joined_at: nostr::Timestamp::now().as_i64(),
 | 
					            admin,
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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<String> = 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<String, String>,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    relays: Option<HashMap<String, Vec<String>>>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<UserRow> for Nip05Profile {
 | 
					 | 
				
			||||||
    fn from(value: UserRow) -> Self {
 | 
					 | 
				
			||||||
        let mut name: HashMap<String, String> = HashMap::new();
 | 
					 | 
				
			||||||
        name.insert(value.username, value.pubkey.clone());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let relay = match value.relays.is_empty() {
 | 
					 | 
				
			||||||
            true => None,
 | 
					 | 
				
			||||||
            false => {
 | 
					 | 
				
			||||||
                let mut relay: HashMap<String, Vec<String>> = HashMap::new();
 | 
					 | 
				
			||||||
                relay.insert(value.pubkey.clone(), value.relays);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Some(relay)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            names: name,
 | 
					 | 
				
			||||||
            relays: relay,
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -117,33 +41,3 @@ pub fn validate_pubkey(value: &str) -> Result<(), ValidationError> {
 | 
				
			||||||
        Err(_) => Err(ValidationError::new("Unable to parse pubkey")),
 | 
					        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<String> = match serde_json::from_str(relays_raw) {
 | 
					 | 
				
			||||||
            Ok(val) => val,
 | 
					 | 
				
			||||||
            Err(_) => vec![],
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        dbg!(relays);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,12 +11,18 @@ pub async fn ws_handler(
 | 
				
			||||||
    Ok(ws.on_upgrade(move |socket| ws::client_connection(socket, context, real_client_ip)))
 | 
					    Ok(ws.on_upgrade(move |socket| ws::client_connection(socket, context, real_client_ip)))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn relay_config(header: String, context: Context) -> Result<impl Reply, Rejection> {
 | 
					pub async fn relay_config(header: String) -> Result<impl Reply, Rejection> {
 | 
				
			||||||
    if header != "application/nostr+json" {
 | 
					    if header != "application/nostr+json" {
 | 
				
			||||||
        Err(warp::reject::not_found())
 | 
					        Err(warp::reject::not_found())
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        let config = context.config.get_relay_config_json();
 | 
					        let res = serde_json::json!({
 | 
				
			||||||
        
 | 
					            "contact": "klink@zhitno.st",
 | 
				
			||||||
        Ok(warp::reply::json(&config))
 | 
					            "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))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,26 +6,25 @@ use warp::{Filter, Rejection, Reply};
 | 
				
			||||||
pub fn routes(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					pub fn routes(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
    let cors = warp::cors().allow_any_origin();
 | 
					    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<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					fn index(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
    // let real_client_ip = warp::header::optional::<std::net::SocketAddr>("X-Real-IP");
 | 
					    // let real_client_ip = warp::header::optional::<std::net::SocketAddr>("X-Real-IP");
 | 
				
			||||||
    let real_client_ip = warp::addr::remote();
 | 
					    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::path::end().and(
 | 
				
			||||||
        warp::header::header("Accept")
 | 
					        accept_application_json_header
 | 
				
			||||||
            .and(with_context(context.clone()))
 | 
					            .and_then(handler::relay_config)
 | 
				
			||||||
            .and_then(handler::relay_config),
 | 
					            .with(&cors)
 | 
				
			||||||
    );
 | 
					            .or(warp::ws()
 | 
				
			||||||
    let nostr_relay_path = warp::path::end().and(
 | 
					                .and(with_context(context))
 | 
				
			||||||
        warp::ws()
 | 
					                .and(real_client_ip)
 | 
				
			||||||
            .and(with_context(context.clone()))
 | 
					                .and_then(handler::ws_handler)
 | 
				
			||||||
            .and(real_client_ip)
 | 
					                .with(&cors)),
 | 
				
			||||||
            .and_then(handler::ws_handler),
 | 
					    )
 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    relay_information_document_path.or(nostr_relay_path)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn static_files() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					fn static_files() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										173
									
								
								src/relay/ws.rs
									
										
									
									
									
								
							
							
						
						
									
										173
									
								
								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() => {
 | 
					            Some(message) = client_receiver.next() => {
 | 
				
			||||||
            match message {
 | 
					            match message {
 | 
				
			||||||
                Ok(message) => {
 | 
					                Ok(message) => {
 | 
				
			||||||
 | 
					                    // ws_sender
 | 
				
			||||||
 | 
					                    //     .send(message)
 | 
				
			||||||
 | 
					                    //     .unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					                    //         log::error!("websocket send error: {}", e);
 | 
				
			||||||
 | 
					                    //     })
 | 
				
			||||||
 | 
					                    //     .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    match ws_sender.send(message).await {
 | 
					                    match ws_sender.send(message).await {
 | 
				
			||||||
                        Ok(_) => (),
 | 
					                        Ok(_) => (),
 | 
				
			||||||
                        Err(e) => {
 | 
					                        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 {} - {}] message: {}",
 | 
				
			||||||
        client.ip(),
 | 
					        client.ip(),
 | 
				
			||||||
        client.client_id,
 | 
					        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) {
 | 
					async fn handle_msg(context: &Context, client: &mut Client, client_message: ClientMessage) {
 | 
				
			||||||
    match client_message {
 | 
					    match client_message {
 | 
				
			||||||
        ClientMessage::Event(event) => {
 | 
					        ClientMessage::Event(event) => handle_event(context, client, event).await,
 | 
				
			||||||
            if needs_auth(context, client, Some(&event)) {
 | 
					 | 
				
			||||||
                request_auth(context, client).await;
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            handle_event(context, client, event).await
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        ClientMessage::Req {
 | 
					        ClientMessage::Req {
 | 
				
			||||||
            subscription_id,
 | 
					            subscription_id,
 | 
				
			||||||
            filters,
 | 
					            filters,
 | 
				
			||||||
        } => {
 | 
					        } => handle_req(context, client, subscription_id, filters).await,
 | 
				
			||||||
            if needs_auth(context, client, None) {
 | 
					 | 
				
			||||||
                request_auth(context, client).await;
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            handle_req(context, client, subscription_id, filters).await
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        ClientMessage::Count {
 | 
					        ClientMessage::Count {
 | 
				
			||||||
            subscription_id,
 | 
					            subscription_id,
 | 
				
			||||||
            filters,
 | 
					            filters,
 | 
				
			||||||
        } => {
 | 
					        } => handle_count(context, client, subscription_id, filters).await,
 | 
				
			||||||
            if needs_auth(context, client, None) {
 | 
					 | 
				
			||||||
                request_auth(context, client).await;
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            handle_count(context, client, subscription_id, filters).await
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        ClientMessage::Close(subscription_id) => handle_close(client, subscription_id).await,
 | 
					        ClientMessage::Close(subscription_id) => handle_close(client, subscription_id).await,
 | 
				
			||||||
        ClientMessage::Auth(event) => handle_auth(context, client, event).await,
 | 
					        ClientMessage::Auth(event) => handle_auth(client, event).await,
 | 
				
			||||||
        // Unhandled messages
 | 
					        _ => (),
 | 
				
			||||||
        _ => unhandled_message(context, client).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<Event>) {
 | 
					async fn handle_event(context: &Context, client: &Client, event: Box<Event>) {
 | 
				
			||||||
    log::debug!("handle_event is processing new event");
 | 
					    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) {
 | 
					async fn handle_close(client: &mut Client, subscription_id: SubscriptionId) {
 | 
				
			||||||
 | 
					    // context.pubsub.send(new nostr event) then handle possible errors
 | 
				
			||||||
    client.unsubscribe(subscription_id);
 | 
					    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<Event>) {
 | 
					async fn handle_auth(client: &Client, event: Box<Event>) {
 | 
				
			||||||
    let mut subscriber = context.pubsub.subscribe(channels::MSG_AUTH).await;
 | 
					    let message = Message::text("AUTH not implemented");
 | 
				
			||||||
    context
 | 
					    send(client, message);
 | 
				
			||||||
        .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
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 crate::utils::error::Error;
 | 
				
			||||||
use nostr::key::XOnlyPublicKey;
 | 
					 | 
				
			||||||
use nostr::prelude::*;
 | 
					use nostr::prelude::*;
 | 
				
			||||||
 | 
					use nostr::{key::XOnlyPublicKey, Keys};
 | 
				
			||||||
use regex::Regex;
 | 
					use regex::Regex;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use validator::Validate;
 | 
					use validator::Validate;
 | 
				
			||||||
| 
						 | 
					@ -10,26 +12,31 @@ lazy_static! {
 | 
				
			||||||
    static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap();
 | 
					    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 {
 | 
					pub struct UserBody {
 | 
				
			||||||
    #[validate(length(min = 1), regex = "VALID_CHARACTERS")]
 | 
					    #[validate(length(min = 1), regex = "VALID_CHARACTERS")]
 | 
				
			||||||
    pub name: String,
 | 
					    pub name: String,
 | 
				
			||||||
    #[validate(custom(function = "validate_pubkey"))]
 | 
					    #[validate(custom(function = "validate_pubkey"))]
 | 
				
			||||||
    pubkey: String,
 | 
					    pub pubkey: String,
 | 
				
			||||||
    #[validate(custom(function = "validate_relays"))]
 | 
					 | 
				
			||||||
    pub relays: Vec<String>
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl UserBody {
 | 
					impl UserBody {
 | 
				
			||||||
    pub fn get_pubkey(&self) -> String {
 | 
					    pub fn get_pubkey(&self) -> XOnlyPublicKey {
 | 
				
			||||||
        nostr::Keys::from_pk_str(&self.pubkey).unwrap().public_key().to_string()
 | 
					        let keys = Keys::from_pk_str(&self.pubkey).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        keys.public_key()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize, Deserialize, Debug, Clone)]
 | 
				
			||||||
 | 
					pub struct Nip05 {
 | 
				
			||||||
 | 
					    names: HashMap<String, String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize, Debug, Validate, Clone)]
 | 
					#[derive(Serialize, Deserialize, Debug, Validate, Clone)]
 | 
				
			||||||
pub struct UserQuery {
 | 
					pub struct UserQuery {
 | 
				
			||||||
    #[validate(length(min = 1))]
 | 
					    #[validate(length(min = 1))]
 | 
				
			||||||
    pub name: String,
 | 
					    pub user: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize, Debug, Clone, Validate)]
 | 
					#[derive(Serialize, Deserialize, Debug, Clone, Validate)]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,10 @@ use crate::utils::error::Error;
 | 
				
			||||||
use validator::Validate;
 | 
					use validator::Validate;
 | 
				
			||||||
use warp::{Filter, Rejection};
 | 
					use warp::{Filter, Rejection};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn with_client_ip() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn with_user_body() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn validate_body_filter<T: serde::de::DeserializeOwned + Send + Validate + 'static>(
 | 
					pub fn validate_body_filter<T: serde::de::DeserializeOwned + Send + Validate + 'static>(
 | 
				
			||||||
) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
 | 
					) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
 | 
				
			||||||
    warp::body::json::<T>().and_then(|query: T| async move {
 | 
					    warp::body::json::<T>().and_then(|query: T| async move {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,6 @@ use crate::utils::structs::Context;
 | 
				
			||||||
use serde_json::json;
 | 
					use serde_json::json;
 | 
				
			||||||
use warp::{Rejection, Reply};
 | 
					use warp::{Rejection, Reply};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::dto::UserBody;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub async fn get_account(
 | 
					pub async fn get_account(
 | 
				
			||||||
    // account: Result<AccountPubkey, Error>,
 | 
					    // account: Result<AccountPubkey, Error>,
 | 
				
			||||||
    account: Account,
 | 
					    account: Account,
 | 
				
			||||||
| 
						 | 
					@ -47,9 +45,14 @@ pub async fn get_account(
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn get_user(user_query: UserQuery, context: Context) -> Result<impl Reply, Rejection> {
 | 
					pub async fn get_user(user_query: UserQuery, context: Context) -> Result<impl Reply, Rejection> {
 | 
				
			||||||
 | 
					    let name = user_query.user;
 | 
				
			||||||
    let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await;
 | 
					    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
 | 
					    context
 | 
				
			||||||
        .pubsub
 | 
					        .pubsub
 | 
				
			||||||
        .publish(
 | 
					        .publish(
 | 
				
			||||||
| 
						 | 
					@ -62,44 +65,18 @@ pub async fn get_user(user_query: UserQuery, context: Context) -> Result<impl Re
 | 
				
			||||||
        .await;
 | 
					        .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Ok(message) = subscriber.recv().await {
 | 
					    if let Ok(message) = subscriber.recv().await {
 | 
				
			||||||
 | 
					        let mut response = json!({"names": {}, "relays": {}});
 | 
				
			||||||
        match message.content {
 | 
					        match message.content {
 | 
				
			||||||
            Command::DbResNIP05(profile) => {
 | 
					            Command::DbResUser(user) => {
 | 
				
			||||||
                let response = serde_json::to_value(profile).unwrap();
 | 
					                response = json!({
 | 
				
			||||||
 | 
					                    "names": {
 | 
				
			||||||
 | 
					                        user.username: user.pubkey
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "relays": {}
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
                Ok(warp::reply::json(&response))
 | 
					                Ok(warp::reply::json(&response))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            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",
 | 
					 | 
				
			||||||
            ))),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        Err(warp::reject::custom(Error::internal_with_message(
 | 
					 | 
				
			||||||
            "Unhandeled message type",
 | 
					 | 
				
			||||||
        )))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub async fn create_user(user_body: UserBody, context: Context) -> Result<impl Reply, Rejection> {
 | 
					 | 
				
			||||||
    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)),
 | 
					 | 
				
			||||||
            _ => Err(warp::reject::custom(Error::internal_with_message(
 | 
					            _ => Err(warp::reject::custom(Error::internal_with_message(
 | 
				
			||||||
                "Unhandeled message type",
 | 
					                "Unhandeled message type",
 | 
				
			||||||
            ))),
 | 
					            ))),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
// mod accounts;
 | 
					mod accounts;
 | 
				
			||||||
pub mod dto;
 | 
					pub mod dto;
 | 
				
			||||||
mod filter;
 | 
					mod filter;
 | 
				
			||||||
mod handler;
 | 
					mod handler;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,69 +1,49 @@
 | 
				
			||||||
use crate::noose::user::User;
 | 
					use crate::noose::user::User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// use super::accounts::create_account;
 | 
					use super::accounts::create_account;
 | 
				
			||||||
use super::dto::{Account, UserBody, UserQuery};
 | 
					use super::dto::{Account, UserQuery};
 | 
				
			||||||
use super::filter::{validate_body_filter, validate_query_filter};
 | 
					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::filter::with_context;
 | 
				
			||||||
use crate::utils::structs::Context;
 | 
					use crate::utils::structs::Context;
 | 
				
			||||||
use warp::{Filter, Rejection, Reply};
 | 
					use warp::{Filter, Rejection, Reply};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn routes(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					pub fn routes(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
    let cors = warp::cors().allow_any_origin();
 | 
					 | 
				
			||||||
    let index = warp::path::end().map(|| warp::reply::html("<h1>SNEED!</h1>"));
 | 
					    let index = warp::path::end().map(|| warp::reply::html("<h1>SNEED!</h1>"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    index
 | 
					    index
 | 
				
			||||||
        .or(nip05_get(context.clone()))
 | 
					        .or(nip05_get(context.clone()))
 | 
				
			||||||
        .or(nip05_create(context.clone()))
 | 
					        .or(account_create(context.clone()))
 | 
				
			||||||
        .with(&cors)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn well_known<M>(warp_method: M) -> impl Filter<Extract = (), Error = Rejection> + Clone
 | 
					pub fn nip05_get(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
where
 | 
					    warp::get()
 | 
				
			||||||
    M: (Filter<Extract = (), Error = Rejection>) + Copy,
 | 
					        .and(warp::path(".well-known"))
 | 
				
			||||||
{
 | 
					        .and(warp::path("nostr.json"))
 | 
				
			||||||
    warp_method.and(warp::path(".well-known"))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn nostr_well_known() -> impl Filter<Extract = (), Error = Rejection> + Clone {
 | 
					 | 
				
			||||||
    well_known(warp::get()).and(warp::path("nostr.json"))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn nip05_get(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					 | 
				
			||||||
    nostr_well_known()
 | 
					 | 
				
			||||||
        .and(validate_query_filter::<UserQuery>())
 | 
					        .and(validate_query_filter::<UserQuery>())
 | 
				
			||||||
        .and(with_context(context.clone()))
 | 
					        .and(with_context(context))
 | 
				
			||||||
        .and_then(get_user)
 | 
					        .and_then(get_user)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn nip05_create(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					pub fn account_create(
 | 
				
			||||||
    well_known(warp::post())
 | 
					    context: Context,
 | 
				
			||||||
        .and(warp::path("nostr.json"))
 | 
					) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
        .and(warp::body::content_length_limit(1024))
 | 
					    warp::path("account")
 | 
				
			||||||
        .and(validate_body_filter::<UserBody>())
 | 
					        .and(warp::post())
 | 
				
			||||||
        .and(with_context(context.clone()))
 | 
					        .and(validate_body_filter::<User>())
 | 
				
			||||||
        .and_then(create_user)
 | 
					        .and(with_context(context))
 | 
				
			||||||
 | 
					        .and_then(create_account)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// pub fn account_create(
 | 
					pub fn account_get(
 | 
				
			||||||
//     context: Context,
 | 
					    context: Context,
 | 
				
			||||||
// ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
//     warp::path("account")
 | 
					    warp::path("account")
 | 
				
			||||||
//         .and(warp::post())
 | 
					        .and(warp::get())
 | 
				
			||||||
//         .and(validate_body_filter::<User>())
 | 
					        .and(validate_body_filter::<Account>())
 | 
				
			||||||
//         .and(with_context(context))
 | 
					        .and(with_context(context))
 | 
				
			||||||
//         .and_then(create_account)
 | 
					        .and_then(get_account)
 | 
				
			||||||
// }
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// pub fn account_get(
 | 
					 | 
				
			||||||
//     context: Context,
 | 
					 | 
				
			||||||
// ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					 | 
				
			||||||
//     warp::path("account")
 | 
					 | 
				
			||||||
//         .and(warp::get())
 | 
					 | 
				
			||||||
//         .and(validate_body_filter::<Account>())
 | 
					 | 
				
			||||||
//         .and(with_context(context))
 | 
					 | 
				
			||||||
//         .and_then(get_account)
 | 
					 | 
				
			||||||
// }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// pub fn account_update(
 | 
					// pub fn account_update(
 | 
				
			||||||
//     context: Context,
 | 
					//     context: Context,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
use super::dto::AccountPubkey;
 | 
					use super::dto::AccountPubkey;
 | 
				
			||||||
use crate::utils::error::Error;
 | 
					use crate::utils::error::Error;
 | 
				
			||||||
use nostr::prelude::FromPkStr;
 | 
					use nostr::prelude::FromPkStr;
 | 
				
			||||||
use validator::{Validate, ValidationError, validate_url};
 | 
					use validator::{Validate, ValidationError};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn validate_account_pubkey_query(
 | 
					pub async fn validate_account_pubkey_query(
 | 
				
			||||||
    account_pubkey: AccountPubkey,
 | 
					    account_pubkey: AccountPubkey,
 | 
				
			||||||
| 
						 | 
					@ -25,18 +25,6 @@ pub fn validate_pubkey(value: &str) -> Result<(), ValidationError> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match nostr::Keys::from_pk_str(value) {
 | 
					    match nostr::Keys::from_pk_str(value) {
 | 
				
			||||||
        Ok(_) => Ok(()),
 | 
					        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<String>) -> Result<(), ValidationError> {
 | 
					 | 
				
			||||||
    if relays.is_empty() {
 | 
					 | 
				
			||||||
        return Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if relays.iter().all(validate_url) {
 | 
					 | 
				
			||||||
        return Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Err(ValidationError::new("Relays have wrong url format"))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
use std::{path::PathBuf, str::FromStr};
 | 
					use std::path::PathBuf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey};
 | 
					use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,6 @@ use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey};
 | 
				
			||||||
pub struct Config {
 | 
					pub struct Config {
 | 
				
			||||||
    admin_pubkey: XOnlyPublicKey,
 | 
					    admin_pubkey: XOnlyPublicKey,
 | 
				
			||||||
    db_path: PathBuf,
 | 
					    db_path: PathBuf,
 | 
				
			||||||
    auth_required: bool,
 | 
					 | 
				
			||||||
    relay_url: nostr::UncheckedUrl,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for Config {
 | 
					impl Default for Config {
 | 
				
			||||||
| 
						 | 
					@ -25,24 +23,13 @@ impl Config {
 | 
				
			||||||
            .public_key();
 | 
					            .public_key();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let db_path = std::env::var("DATABASE_URL").map(PathBuf::from).unwrap();
 | 
					        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 {
 | 
					        Self {
 | 
				
			||||||
            admin_pubkey,
 | 
					            admin_pubkey,
 | 
				
			||||||
            db_path,
 | 
					            db_path,
 | 
				
			||||||
            auth_required,
 | 
					 | 
				
			||||||
            relay_url,
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn auth_required(&self) -> bool {
 | 
					 | 
				
			||||||
        self.auth_required
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_admin_pubkey(&self) -> &XOnlyPublicKey {
 | 
					    pub fn get_admin_pubkey(&self) -> &XOnlyPublicKey {
 | 
				
			||||||
        &self.admin_pubkey
 | 
					        &self.admin_pubkey
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -51,18 +38,14 @@ impl Config {
 | 
				
			||||||
        self.db_path.clone()
 | 
					        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 {
 | 
					    pub fn get_relay_config_json(&self) -> serde_json::Value {
 | 
				
			||||||
        serde_json::json!({
 | 
					        serde_json::json!({
 | 
				
			||||||
            "contact": "klink@zhitno.st",
 | 
					            "contact": "klink@zhitno.st",
 | 
				
			||||||
            "name": "zhitno.st",
 | 
					            "name": "zhitno.st",
 | 
				
			||||||
            "description": "Very *special* nostr relay",
 | 
					            "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",
 | 
					            "software": "git+https://git.zhitno.st/Klink/sneedstr.git",
 | 
				
			||||||
            "version": "0.1.1"
 | 
					            "version": "0.1.0"
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,13 +8,13 @@ use std::{
 | 
				
			||||||
use validator::ValidationErrors;
 | 
					use validator::ValidationErrors;
 | 
				
			||||||
use warp::{http::StatusCode, reject::Reject};
 | 
					use warp::{http::StatusCode, reject::Reject};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
 | 
					#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
 | 
				
			||||||
pub struct Error {
 | 
					pub struct Error {
 | 
				
			||||||
    pub code: u16,
 | 
					    pub code: u16,
 | 
				
			||||||
    pub message: String,
 | 
					    pub message: String,
 | 
				
			||||||
    pub sneedstr_version: String,
 | 
					    /// Sneedstr version.
 | 
				
			||||||
 | 
					    #[serde(skip_serializing_if = "Option::is_none")]
 | 
				
			||||||
 | 
					    pub sneedstr_version: Option<u16>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl StdError for Error {
 | 
					impl StdError for Error {
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,7 @@ impl Error {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            code: code.as_u16(),
 | 
					            code: code.as_u16(),
 | 
				
			||||||
            message,
 | 
					            message,
 | 
				
			||||||
            sneedstr_version: VERSION.to_string(),
 | 
					            sneedstr_version: None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,11 +54,12 @@ impl Error {
 | 
				
			||||||
        Self::new(StatusCode::BAD_REQUEST, message)
 | 
					        Self::new(StatusCode::BAD_REQUEST, message)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn not_found<S: Display>(resource: &str, identifier: S) -> Self {
 | 
					    pub fn not_found<S: Display>(resource: &str, identifier: S, service_version: u16) -> Self {
 | 
				
			||||||
        Self::new(
 | 
					        Self::new(
 | 
				
			||||||
            StatusCode::NOT_FOUND,
 | 
					            StatusCode::NOT_FOUND,
 | 
				
			||||||
            format!("{} not found by {}", resource, identifier),
 | 
					            format!("{} not found by {}", resource, identifier),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        .sneedstr_version(service_version)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn invalid_param<S: Display>(name: &str, value: S) -> Self {
 | 
					    pub fn invalid_param<S: Display>(name: &str, value: S) -> Self {
 | 
				
			||||||
| 
						 | 
					@ -76,6 +77,11 @@ impl Error {
 | 
				
			||||||
    pub fn status_code(&self) -> StatusCode {
 | 
					    pub fn status_code(&self) -> StatusCode {
 | 
				
			||||||
        StatusCode::from_u16(self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
 | 
					        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 {
 | 
					impl fmt::Display for Error {
 | 
				
			||||||
| 
						 | 
					@ -110,36 +116,28 @@ mod tests {
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn test_to_string() {
 | 
					    fn test_to_string() {
 | 
				
			||||||
        let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned());
 | 
					        let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned());
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(err.to_string(), "400 Bad Request: invalid address")
 | 
				
			||||||
            err.to_string(),
 | 
					 | 
				
			||||||
            "Error { code: 400, message: 'invalid address', sneedstr_version: \"0.1.1\" }"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn test_from_anyhow_error_as_internal_error() {
 | 
					    fn test_from_anyhow_error_as_internal_error() {
 | 
				
			||||||
        let err = Error::from(anyhow::format_err!("hello"));
 | 
					        let err = Error::from(anyhow::format_err!("hello"));
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(err.to_string(), "500 Internal Server Error: hello")
 | 
				
			||||||
            err.to_string(),
 | 
					 | 
				
			||||||
            "Error { code: 500, message: 'hello', sneedstr_version: \"0.1.1\" }"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn test_to_string_with_sneedstr_version() {
 | 
					    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!(
 | 
					        assert_eq!(
 | 
				
			||||||
            err.to_string(),
 | 
					            err.to_string(),
 | 
				
			||||||
            "Error { code: 400, message: 'invalid address', sneedstr_version: \"0.1.1\" }"
 | 
					            "400 Bad Request: invalid address\ndiem ledger version: 123"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn test_internal_error() {
 | 
					    fn test_internal_error() {
 | 
				
			||||||
        let err = Error::internal(anyhow::format_err!("hello"));
 | 
					        let err = Error::internal(anyhow::format_err!("hello"));
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(err.to_string(), "500 Internal Server Error: hello")
 | 
				
			||||||
            err.to_string(),
 | 
					 | 
				
			||||||
            "Error { code: 500, message: 'hello', sneedstr_version: \"0.1.1\" }"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ use crate::PubSub;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use nostr::{Event, Filter, SubscriptionId};
 | 
					use nostr::{Event, Filter, SubscriptionId};
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::str::FromStr;
 | 
					 | 
				
			||||||
use std::sync::Arc;
 | 
					use std::sync::Arc;
 | 
				
			||||||
use tokio::sync::mpsc;
 | 
					use tokio::sync::mpsc;
 | 
				
			||||||
use uuid::Uuid;
 | 
					use uuid::Uuid;
 | 
				
			||||||
| 
						 | 
					@ -51,8 +50,6 @@ pub struct Client {
 | 
				
			||||||
    pub client_id: Uuid,
 | 
					    pub client_id: Uuid,
 | 
				
			||||||
    pub client_connection: Option<mpsc::UnboundedSender<Result<Message, Error>>>,
 | 
					    pub client_connection: Option<mpsc::UnboundedSender<Result<Message, Error>>>,
 | 
				
			||||||
    pub subscriptions: HashMap<String, Subscription>,
 | 
					    pub subscriptions: HashMap<String, Subscription>,
 | 
				
			||||||
    pub authenticated: bool, // NIP-42
 | 
					 | 
				
			||||||
    challlenge: Option<String>, // NIP-42
 | 
					 | 
				
			||||||
    max_subs: usize,
 | 
					    max_subs: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,8 +60,6 @@ impl Client {
 | 
				
			||||||
            client_id: Uuid::new_v4(),
 | 
					            client_id: Uuid::new_v4(),
 | 
				
			||||||
            client_connection: None,
 | 
					            client_connection: None,
 | 
				
			||||||
            subscriptions: HashMap::new(),
 | 
					            subscriptions: HashMap::new(),
 | 
				
			||||||
            authenticated: false,
 | 
					 | 
				
			||||||
            challlenge: None,
 | 
					 | 
				
			||||||
            max_subs: MAX_SUBSCRIPTIONS,
 | 
					            max_subs: MAX_SUBSCRIPTIONS,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -73,43 +68,6 @@ impl Client {
 | 
				
			||||||
        &self.client_ip_addr
 | 
					        &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> {
 | 
					    pub fn subscribe(&mut self, subscription: Subscription) -> Result<(), Error> {
 | 
				
			||||||
        let k = subscription.get_id();
 | 
					        let k = subscription.get_id();
 | 
				
			||||||
        let sub_id_len = k.len();
 | 
					        let sub_id_len = k.len();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										66
									
								
								templates/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								templates/index.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="utf-8">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
 | 
					    <title>Registration</title>
 | 
				
			||||||
 | 
					    </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <div id="app">
 | 
				
			||||||
 | 
					      <h1>Create new user</h1>
 | 
				
			||||||
 | 
					        <form action="/register" method="post" id="signup">
 | 
				
			||||||
 | 
					          <h1>Sign Up</h1>
 | 
				
			||||||
 | 
						  <div class="field">
 | 
				
			||||||
 | 
						    <label for="name">Name:</label>
 | 
				
			||||||
 | 
						    <input type="text" id="name" name="name" placeholder="Enter your profile name" />
 | 
				
			||||||
 | 
						    <small></small>
 | 
				
			||||||
 | 
						  </div>
 | 
				
			||||||
 | 
						  <div class="field">
 | 
				
			||||||
 | 
						    <label for="email">PubKey:</label>
 | 
				
			||||||
 | 
						    <input type="text" id="pubkey" name="pubkey" placeholder="Enter your pubkey" />
 | 
				
			||||||
 | 
						    <small></small>
 | 
				
			||||||
 | 
						  </div>
 | 
				
			||||||
 | 
						  <button type="submit">Register</button>
 | 
				
			||||||
 | 
					        </form> 
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					  <script>
 | 
				
			||||||
 | 
					    const form = document.getElementById('signup');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    form.addEventListener('submit', async (event) => {
 | 
				
			||||||
 | 
					      // stop form submission
 | 
				
			||||||
 | 
						event.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const name = form.elements['name'];
 | 
				
			||||||
 | 
						const pubkey = form.elements['pubkey'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// getting the element's value
 | 
				
			||||||
 | 
						let userName = name.value;
 | 
				
			||||||
 | 
						let pubKey = pubkey.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`${userName}:${pubKey}`);
 | 
				
			||||||
 | 
						const data = { name: userName, pubkey: pubKey};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send user data
 | 
				
			||||||
 | 
						await postJSON(data);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function postJSON(data) {
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
						    const response = await fetch("http://localhost:8085/register", {
 | 
				
			||||||
 | 
							method: "POST",
 | 
				
			||||||
 | 
							headers: {
 | 
				
			||||||
 | 
							    "Content-Type": "application/json",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							body: JSON.stringify(data),
 | 
				
			||||||
 | 
						    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						    const result = await response.json();
 | 
				
			||||||
 | 
						    console.log("Success:", result);
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
						    console.error("Error:", error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  </script>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										70
									
								
								templates/index.stpl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								templates/index.stpl
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,70 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="utf-8">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
 | 
					    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
 | 
				
			||||||
 | 
					    <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
 | 
				
			||||||
 | 
					    <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
 | 
				
			||||||
 | 
					    <link rel="manifest" href="/site.webmanifest">
 | 
				
			||||||
 | 
					    <title>Registration</title>
 | 
				
			||||||
 | 
					    </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <div id="app">
 | 
				
			||||||
 | 
					      <h1>Create new user</h1>
 | 
				
			||||||
 | 
					        <form action="/register" method="post" id="signup">
 | 
				
			||||||
 | 
					          <h1>Sign Up</h1>
 | 
				
			||||||
 | 
						  <div class="field">
 | 
				
			||||||
 | 
						    <label for="name">Name:</label>
 | 
				
			||||||
 | 
						    <input type="text" id="name" name="name" placeholder="Enter your profile name" />
 | 
				
			||||||
 | 
						    <small></small>
 | 
				
			||||||
 | 
						  </div>
 | 
				
			||||||
 | 
						  <div class="field">
 | 
				
			||||||
 | 
						    <label for="email">PubKey:</label>
 | 
				
			||||||
 | 
						    <input type="text" id="pubkey" name="pubkey" placeholder="Enter your pubkey" />
 | 
				
			||||||
 | 
						    <small></small>
 | 
				
			||||||
 | 
						  </div>
 | 
				
			||||||
 | 
						  <button type="submit">Register</button>
 | 
				
			||||||
 | 
					        </form> 
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					  <script>
 | 
				
			||||||
 | 
					    const form = document.getElementById('signup');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    form.addEventListener('submit', async (event) => {
 | 
				
			||||||
 | 
					      // stop form submission
 | 
				
			||||||
 | 
						event.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const name = form.elements['name'];
 | 
				
			||||||
 | 
						const pubkey = form.elements['pubkey'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// getting the element's value
 | 
				
			||||||
 | 
						let userName = name.value;
 | 
				
			||||||
 | 
						let pubKey = pubkey.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`${userName}:${pubKey}`);
 | 
				
			||||||
 | 
						const data = { name: userName, pubkey: pubKey};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send user data
 | 
				
			||||||
 | 
						await postJSON(data);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function postJSON(data) {
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
						    const response = await fetch("http://localhost:8085/register", {
 | 
				
			||||||
 | 
							method: "POST",
 | 
				
			||||||
 | 
							headers: {
 | 
				
			||||||
 | 
							    "Content-Type": "application/json",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							body: JSON.stringify(data),
 | 
				
			||||||
 | 
						    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						    const result = await response.json();
 | 
				
			||||||
 | 
						    console.log("Success:", result);
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
						    console.error("Error:", error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  </script>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								templates/static/android-chrome-192x192.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								templates/static/android-chrome-192x192.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 43 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								templates/static/android-chrome-512x512.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								templates/static/android-chrome-512x512.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 235 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								templates/static/apple-touch-icon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								templates/static/apple-touch-icon.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								templates/static/favicon-16x16.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								templates/static/favicon-16x16.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 777 B  | 
							
								
								
									
										
											BIN
										
									
								
								templates/static/favicon-32x32.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								templates/static/favicon-32x32.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								templates/static/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								templates/static/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										1
									
								
								templates/static/site.webmanifest
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								templates/static/site.webmanifest
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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"}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue