Compare commits
	
		
			10 commits
		
	
	
		
			255905021c
			...
			e40a4645a3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e40a4645a3 | |||
| 1f7270dabf | |||
| 5944414d15 | |||
| b6dcf61995 | |||
| d06206bb24 | |||
| f7b74bd22c | |||
| 377da44eed | |||
| e1306608ef | |||
| 645d125077 | |||
| bf08ac12e0 | 
					 35 changed files with 1405 additions and 665 deletions
				
			
		
							
								
								
									
										92
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										92
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -497,18 +497,6 @@ 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"
 | 
				
			||||||
| 
						 | 
					@ -761,15 +749,6 @@ 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"
 | 
				
			||||||
| 
						 | 
					@ -957,12 +936,6 @@ 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"
 | 
				
			||||||
| 
						 | 
					@ -1591,44 +1564,6 @@ 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"
 | 
				
			||||||
| 
						 | 
					@ -1741,15 +1676,6 @@ 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"
 | 
				
			||||||
| 
						 | 
					@ -1833,7 +1759,6 @@ 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",
 | 
				
			||||||
| 
						 | 
					@ -2062,26 +1987,11 @@ 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"
 | 
				
			||||||
| 
						 | 
					@ -2090,8 +2000,6 @@ 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,7 +26,6 @@ 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,7 +13,6 @@
 | 
				
			||||||
        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`:
 | 
				
			||||||
| 
						 | 
					@ -25,7 +24,6 @@
 | 
				
			||||||
            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/
 | 
					 | 
				
			||||||
          '';
 | 
					          '';
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,6 +50,8 @@
 | 
				
			||||||
          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,6 +21,11 @@ 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;
 | 
				
			||||||
| 
						 | 
					@ -36,6 +41,10 @@ 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 {
 | 
				
			||||||
| 
						 | 
					@ -64,6 +73,8 @@ 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;
 | 
				
			||||||
| 
						 | 
					@ -111,6 +122,10 @@ 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,15 +1,16 @@
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    noose::sled::BanInfo,
 | 
					    noose::sled::BanInfo,
 | 
				
			||||||
    noose::user::{User, UserRow},
 | 
					    noose::user::{User, UserRow, Nip05Profile},
 | 
				
			||||||
    utils::{error::Error, structs::Subscription},
 | 
					    utils::{error::Error, structs::Subscription}, usernames::dto::UserBody,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use nostr::secp256k1::XOnlyPublicKey;
 | 
					use nostr::secp256k1::XOnlyPublicKey;
 | 
				
			||||||
use std::{collections::HashMap, fmt::Debug};
 | 
					use std::{collections::{HashMap, BTreeSet}, 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";
 | 
				
			||||||
| 
						 | 
					@ -18,18 +19,21 @@ 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,
 | 
				
			||||||
    // DbResponse
 | 
					    // NIP-05 related messages
 | 
				
			||||||
 | 
					    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>,
 | 
				
			||||||
| 
						 | 
					@ -38,26 +42,48 @@ 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,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,7 +112,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(32); // 32 is the channel capacity
 | 
					        let (tx, _rx) = broadcast::channel(20_000); // 20000 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,17 +1,58 @@
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    bussy::PubSub,
 | 
					    bussy::PubSub,
 | 
				
			||||||
 | 
					    usernames::dto::UserBody,
 | 
				
			||||||
    utils::{error::Error, structs::Subscription},
 | 
					    utils::{error::Error, structs::Subscription},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use nostr::{Event, RelayMessage};
 | 
					use nostr::{secp256k1::XOnlyPublicKey, Event, RelayMessage};
 | 
				
			||||||
use std::sync::Arc;
 | 
					use nostr_database::Profile;
 | 
				
			||||||
 | 
					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>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/noose/migrations/1706575155557_nip05.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/noose/migrations/1706575155557_nip05.sql
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/noose/migrations/1707327016995_nip42_profile.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/noose/migrations/1707327016995_nip42_profile.sql
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					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,7 +11,8 @@ 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_pragma = include_str!("./1697410424624_pragma.sql");
 | 
					        let m_nip05 = include_str!("./1706575155557_nip05.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),
 | 
				
			||||||
| 
						 | 
					@ -20,7 +21,8 @@ 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_pragma),
 | 
					            M::up(m_nip05),
 | 
				
			||||||
 | 
					            M::up(m_nip42),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match migrations.to_latest(connection) {
 | 
					        match migrations.to_latest(connection) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ 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.clone()).await {
 | 
					                    match self.handle_event(client_id, event).await {
 | 
				
			||||||
                        Ok(_) => {
 | 
					                        Ok(_) => {
 | 
				
			||||||
                            log::info!("[Pipeline] handle_event completed");
 | 
					                            log::info!("[Pipeline] handle_event completed");
 | 
				
			||||||
                            channel = message.source;
 | 
					                            channel = message.source;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,197 +1,27 @@
 | 
				
			||||||
use super::{db::Noose, migrations::MigrationRunner};
 | 
					use super::{
 | 
				
			||||||
 | 
					    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, Event, EventId, Filter, RelayMessage, TagKind, Timestamp, Url,
 | 
					    nips::nip01::Coordinate, secp256k1::XOnlyPublicKey, Event, EventId, Filter, JsonUtil,
 | 
				
			||||||
 | 
					    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};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					use crate::noose::sqlite_tables::*;
 | 
				
			||||||
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 {
 | 
				
			||||||
| 
						 | 
					@ -212,7 +42,22 @@ 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> {
 | 
				
			||||||
| 
						 | 
					@ -496,86 +341,159 @@ impl NostrSqlite {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        return Ok(false);
 | 
					                        return Ok(false);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					
 | 
				
			||||||
                    log::debug!("inserting new event in events");
 | 
					                    match tx.commit() {
 | 
				
			||||||
                    // Insert into Events table
 | 
					                        Ok(_) => return Ok(true),
 | 
				
			||||||
                    let (sql, values) = Query::insert()
 | 
					                        Err(err) => {
 | 
				
			||||||
                        .into_table(EventsTable::Table)
 | 
					                            log::error!("Error during transaction commit: {}", err);
 | 
				
			||||||
                        .columns([
 | 
					                            return Ok(false);
 | 
				
			||||||
                            EventsTable::EventId,
 | 
					                        }
 | 
				
			||||||
                            EventsTable::Content,
 | 
					                    }
 | 
				
			||||||
                            EventsTable::Kind,
 | 
					                }
 | 
				
			||||||
                            EventsTable::Pubkey,
 | 
					
 | 
				
			||||||
                            EventsTable::CreatedAt,
 | 
					                if event.kind() == nostr::Kind::Metadata {
 | 
				
			||||||
                            EventsTable::Tags,
 | 
					                    // Try to delete old profile first
 | 
				
			||||||
                            EventsTable::Sig,
 | 
					                    let (sql, value) = Query::delete()
 | 
				
			||||||
                        ])
 | 
					                        .from_table(ProfilesTable::Table)
 | 
				
			||||||
                        .values_panic([
 | 
					                        .and_where(sea_query::Expr::col(ProfilesTable::Pubkey).eq(pubkey.clone()))
 | 
				
			||||||
                            id.clone().into(),
 | 
					 | 
				
			||||||
                            content.clone().into(),
 | 
					 | 
				
			||||||
                            kind.into(),
 | 
					 | 
				
			||||||
                            pubkey.into(),
 | 
					 | 
				
			||||||
                            created_at.into(),
 | 
					 | 
				
			||||||
                            tags.into(),
 | 
					 | 
				
			||||||
                            sig.into(),
 | 
					 | 
				
			||||||
                        ])
 | 
					 | 
				
			||||||
                        .build_rusqlite(SqliteQueryBuilder);
 | 
					                        .build_rusqlite(SqliteQueryBuilder);
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) {
 | 
					                    if let Err(err) = tx.execute(sql.as_str(), &*value.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 delete old Profile record: {}", err);
 | 
				
			||||||
                        return Ok(false);
 | 
					                        return Ok(false);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    // Insert into Tags table
 | 
					                    let (sql, value) = Query::insert()
 | 
				
			||||||
                    log::debug!("inserting new event into tags");
 | 
					                        .into_table(ProfilesTable::Table)
 | 
				
			||||||
                    for tag in event.tags.clone() {
 | 
					                        .columns([ProfilesTable::Pubkey])
 | 
				
			||||||
                        if Self::tag_is_indexable(&tag) {
 | 
					                        .values_panic([pubkey.clone().into()])
 | 
				
			||||||
                            let tag = tag.to_vec();
 | 
					                        .build_rusqlite(SqliteQueryBuilder);
 | 
				
			||||||
                            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())
 | 
					                    if let Err(err) = tx.execute(sql.as_str(), &*value.as_params()) {
 | 
				
			||||||
                                    {
 | 
					                        tx.rollback().unwrap();
 | 
				
			||||||
                                        log::error!(
 | 
					 | 
				
			||||||
                                            "Error inserting event into 'tags' table: {}",
 | 
					 | 
				
			||||||
                                            err
 | 
					 | 
				
			||||||
                                        );
 | 
					 | 
				
			||||||
                                        tx.rollback().unwrap();
 | 
					 | 
				
			||||||
                        
 | 
					                        
 | 
				
			||||||
                                        return Ok(false);
 | 
					                        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([
 | 
				
			||||||
 | 
					                            MetadataTable::Pubkey,
 | 
				
			||||||
 | 
					                            MetadataTable::Name,
 | 
				
			||||||
 | 
					                            MetadataTable::DisplayName,
 | 
				
			||||||
 | 
					                            MetadataTable::About,
 | 
				
			||||||
 | 
					                            MetadataTable::Website,
 | 
				
			||||||
 | 
					                            MetadataTable::Picture,
 | 
				
			||||||
 | 
					                            MetadataTable::Banner,
 | 
				
			||||||
 | 
					                            MetadataTable::Nip05,
 | 
				
			||||||
 | 
					                            MetadataTable::Lud06,
 | 
				
			||||||
 | 
					                            MetadataTable::Lud16,
 | 
				
			||||||
 | 
					                            MetadataTable::Custom,
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        .values_panic([
 | 
				
			||||||
 | 
					                            pubkey.clone().into(),
 | 
				
			||||||
 | 
					                            metadata.name.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata.display_name.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata.about.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata.website.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata.picture.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata.banner.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata.nip05.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata.lud06.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata.lud16.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                            metadata_custom_fields.unwrap_or_default().into(),
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        .build_rusqlite(SqliteQueryBuilder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if let Err(err) = tx.execute(sql.as_str(), &*value.as_params()) {
 | 
				
			||||||
 | 
					                        tx.rollback().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        log::debug!("Failed to store Metadata: {}", err);
 | 
				
			||||||
 | 
					                        return Ok(false);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                log::debug!("inserting new event in events");
 | 
				
			||||||
 | 
					                // Insert into Events table
 | 
				
			||||||
 | 
					                let (sql, values) = Query::insert()
 | 
				
			||||||
 | 
					                    .into_table(EventsTable::Table)
 | 
				
			||||||
 | 
					                    .columns([
 | 
				
			||||||
 | 
					                        EventsTable::EventId,
 | 
				
			||||||
 | 
					                        EventsTable::Content,
 | 
				
			||||||
 | 
					                        EventsTable::Kind,
 | 
				
			||||||
 | 
					                        EventsTable::Pubkey,
 | 
				
			||||||
 | 
					                        EventsTable::CreatedAt,
 | 
				
			||||||
 | 
					                        EventsTable::Tags,
 | 
				
			||||||
 | 
					                        EventsTable::Sig,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    .values_panic([
 | 
				
			||||||
 | 
					                        id.clone().into(),
 | 
				
			||||||
 | 
					                        content.clone().into(),
 | 
				
			||||||
 | 
					                        kind.into(),
 | 
				
			||||||
 | 
					                        pubkey.into(),
 | 
				
			||||||
 | 
					                        created_at.into(),
 | 
				
			||||||
 | 
					                        tags.into(),
 | 
				
			||||||
 | 
					                        sig.into(),
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    .build_rusqlite(SqliteQueryBuilder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) {
 | 
				
			||||||
 | 
					                    log::error!("Error inserting event into 'events' table: {}", err);
 | 
				
			||||||
 | 
					                    tx.rollback().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    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);
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
| 
						 | 
					@ -844,6 +762,12 @@ 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
 | 
				
			||||||
| 
						 | 
					@ -1059,7 +983,9 @@ 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();
 | 
				
			||||||
                    event_vec.push(event);
 | 
					                    if !event.is_expired() {
 | 
				
			||||||
 | 
					                        event_vec.push(event);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Ok(event_vec)
 | 
					                Ok(event_vec)
 | 
				
			||||||
| 
						 | 
					@ -1134,8 +1060,6 @@ 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1144,9 +1068,10 @@ 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_id_string: String = row.get(0).unwrap();
 | 
					                    let event = EventRow::from(row).to_event();
 | 
				
			||||||
                    let event_id = EventId::from_str(&event_id_string).unwrap();
 | 
					                    if !event.is_expired() {
 | 
				
			||||||
                    event_vec.push(event_id);
 | 
					                        event_vec.push(event.id);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Ok(event_vec)
 | 
					                Ok(event_vec)
 | 
				
			||||||
| 
						 | 
					@ -1178,33 +1103,37 @@ impl NostrSqlite {
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let Ok(query_result) = connection
 | 
					        let Ok(query_result) = connection
 | 
				
			||||||
            .interact(move |conn: &mut rusqlite::Connection| -> Result<bool, Error> {
 | 
					            .interact(
 | 
				
			||||||
                let (sql, value) = Query::select()
 | 
					                move |conn: &mut rusqlite::Connection| -> Result<bool, Error> {
 | 
				
			||||||
                    .from(EventsTable::Table)
 | 
					                    let (sql, value) = Query::select()
 | 
				
			||||||
                    .columns([EventsTable::EventId, EventsTable::CreatedAt])
 | 
					                        .from(EventsTable::Table)
 | 
				
			||||||
                    .left_join(
 | 
					                        .columns([EventsTable::EventId, EventsTable::CreatedAt])
 | 
				
			||||||
                        TagsTable::Table,
 | 
					                        .left_join(
 | 
				
			||||||
                        sea_query::Expr::col((TagsTable::Table, TagsTable::EventId))
 | 
					                            TagsTable::Table,
 | 
				
			||||||
                            .equals((EventsTable::Table, EventsTable::EventId)),
 | 
					                            sea_query::Expr::col((TagsTable::Table, TagsTable::EventId))
 | 
				
			||||||
                    )
 | 
					                                .equals((EventsTable::Table, EventsTable::EventId)),
 | 
				
			||||||
                    .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Tag)).eq("a"))
 | 
					                        )
 | 
				
			||||||
                    .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Value)).eq(ident))
 | 
					                        .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Tag)).eq("a"))
 | 
				
			||||||
                    .and_where(
 | 
					                        .and_where(
 | 
				
			||||||
                        sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt))
 | 
					                            sea_query::Expr::col((TagsTable::Table, TagsTable::Value)).eq(ident),
 | 
				
			||||||
                            .gte(timestamp.as_i64()),
 | 
					                        )
 | 
				
			||||||
                    )
 | 
					                        .and_where(
 | 
				
			||||||
                    .limit(1)
 | 
					                            sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt))
 | 
				
			||||||
                    .build_rusqlite(SqliteQueryBuilder);
 | 
					                                .gte(timestamp.as_i64()),
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .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(
 | 
				
			||||||
| 
						 | 
					@ -1214,6 +1143,147 @@ 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 {
 | 
				
			||||||
| 
						 | 
					@ -1272,7 +1342,8 @@ 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).await
 | 
					        self.has_coordinate_been_deleted(coordinate, timestamp)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Set [`EventId`] as seen by relay
 | 
					    /// Set [`EventId`] as seen by relay
 | 
				
			||||||
| 
						 | 
					@ -1339,6 +1410,7 @@ 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),
 | 
				
			||||||
| 
						 | 
					@ -1363,6 +1435,44 @@ 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 {
 | 
				
			||||||
| 
						 | 
					@ -1386,7 +1496,6 @@ 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, "");
 | 
				
			||||||
| 
						 | 
					@ -1427,11 +1536,57 @@ 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]
 | 
				
			||||||
| 
						 | 
					@ -1656,6 +1811,29 @@ 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());
 | 
				
			||||||
| 
						 | 
					@ -1696,4 +1874,14 @@ 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());
 | 
				
			||||||
 | 
					    // }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										322
									
								
								src/noose/sqlite_tables.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								src/noose/sqlite_tables.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,322 @@
 | 
				
			||||||
 | 
					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,27 +1,103 @@
 | 
				
			||||||
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();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
 | 
					pub enum Nip05Table {
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Pubkey,
 | 
				
			||||||
 | 
					    Username,
 | 
				
			||||||
 | 
					    Relays,
 | 
				
			||||||
 | 
					    JoinedAt,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl sea_query::Iden for Nip05Table {
 | 
				
			||||||
 | 
					    fn unquoted(&self, s: &mut dyn std::fmt::Write) {
 | 
				
			||||||
 | 
					        write!(
 | 
				
			||||||
 | 
					            s,
 | 
				
			||||||
 | 
					            "{}",
 | 
				
			||||||
 | 
					            match self {
 | 
				
			||||||
 | 
					                Self::Table => "nip05",
 | 
				
			||||||
 | 
					                Self::Pubkey => "pubkey",
 | 
				
			||||||
 | 
					                Self::Username => "username",
 | 
				
			||||||
 | 
					                Self::Relays => "relays",
 | 
				
			||||||
 | 
					                Self::JoinedAt => "joined_at",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
 | 
				
			||||||
pub struct UserRow {
 | 
					pub struct UserRow {
 | 
				
			||||||
    pub pubkey: String,
 | 
					    pub pubkey: String,
 | 
				
			||||||
    pub username: String,
 | 
					    pub username: String,
 | 
				
			||||||
    inserted_at: i64,
 | 
					    relays: Vec<String>,
 | 
				
			||||||
    admin: bool,
 | 
					    joined_at: i64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl UserRow {
 | 
					impl UserRow {
 | 
				
			||||||
    pub fn new(pubkey: String, username: String, admin: bool) -> Self {
 | 
					    pub fn new(pubkey: String, username: String, relays: Vec<String>) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            pubkey,
 | 
					            pubkey,
 | 
				
			||||||
            username,
 | 
					            username,
 | 
				
			||||||
            inserted_at: Utc::now().timestamp(),
 | 
					            relays,
 | 
				
			||||||
            admin,
 | 
					            joined_at: nostr::Timestamp::now().as_i64(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<&Row<'_>> for UserRow {
 | 
				
			||||||
 | 
					    fn from(row: &Row) -> Self {
 | 
				
			||||||
 | 
					        let pubkey: String = row.get("pubkey").unwrap();
 | 
				
			||||||
 | 
					        let username: String = row.get("username").unwrap();
 | 
				
			||||||
 | 
					        let relays_raw: String = row.get("relays").unwrap_or_default();
 | 
				
			||||||
 | 
					        let joined_at: i64 = row.get("joined_at").unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let relays: Vec<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,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -41,3 +117,33 @@ 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,18 +11,12 @@ 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) -> Result<impl Reply, Rejection> {
 | 
					pub async fn relay_config(header: String, context: Context) -> 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 res = serde_json::json!({
 | 
					        let config = context.config.get_relay_config_json();
 | 
				
			||||||
            "contact": "klink@zhitno.st",
 | 
					        
 | 
				
			||||||
            "name": "zhitno.st",
 | 
					        Ok(warp::reply::json(&config))
 | 
				
			||||||
            "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,25 +6,26 @@ 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();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    warp::path::end().and(
 | 
					    let relay_information_document_path = warp::path::end().and(
 | 
				
			||||||
        accept_application_json_header
 | 
					        warp::header::header("Accept")
 | 
				
			||||||
            .and_then(handler::relay_config)
 | 
					            .and(with_context(context.clone()))
 | 
				
			||||||
            .with(&cors)
 | 
					            .and_then(handler::relay_config),
 | 
				
			||||||
            .or(warp::ws()
 | 
					    );
 | 
				
			||||||
                .and(with_context(context))
 | 
					    let nostr_relay_path = warp::path::end().and(
 | 
				
			||||||
                .and(real_client_ip)
 | 
					        warp::ws()
 | 
				
			||||||
                .and_then(handler::ws_handler)
 | 
					            .and(with_context(context.clone()))
 | 
				
			||||||
                .with(&cors)),
 | 
					            .and(real_client_ip)
 | 
				
			||||||
    )
 | 
					            .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,6 +94,17 @@ 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();
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
                    _ => ()
 | 
					                    _ => ()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,13 +112,6 @@ 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) => {
 | 
				
			||||||
| 
						 | 
					@ -194,7 +198,7 @@ async fn socket_on_message(context: &Context, client: &mut Client, msg: Message)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log::info!(
 | 
					    log::debug!(
 | 
				
			||||||
        "[client {} - {}] message: {}",
 | 
					        "[client {} - {}] message: {}",
 | 
				
			||||||
        client.ip(),
 | 
					        client.ip(),
 | 
				
			||||||
        client.client_id,
 | 
					        client.client_id,
 | 
				
			||||||
| 
						 | 
					@ -211,23 +215,89 @@ 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) => handle_event(context, client, event).await,
 | 
					        ClientMessage::Event(event) => {
 | 
				
			||||||
 | 
					            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(client, event).await,
 | 
					        ClientMessage::Auth(event) => handle_auth(context, 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");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -328,14 +398,77 @@ 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(client: &Client, event: Box<Event>) {
 | 
					async fn handle_auth(context: &Context, client: &mut Client, event: Box<Event>) {
 | 
				
			||||||
    let message = Message::text("AUTH not implemented");
 | 
					    let mut subscriber = context.pubsub.subscribe(channels::MSG_AUTH).await;
 | 
				
			||||||
    send(client, message);
 | 
					    context
 | 
				
			||||||
 | 
					        .pubsub
 | 
				
			||||||
 | 
					        .publish(
 | 
				
			||||||
 | 
					            channels::MSG_NOOSE,
 | 
				
			||||||
 | 
					            crate::bussy::Message {
 | 
				
			||||||
 | 
					                source: channels::MSG_AUTH,
 | 
				
			||||||
 | 
					                content: crate::bussy::Command::DbReqGetProfile(event.pubkey),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut message = nostr::RelayMessage::ok(
 | 
				
			||||||
 | 
					        event.id,
 | 
				
			||||||
 | 
					        client.authenticated,
 | 
				
			||||||
 | 
					        "auth-required: User not registered",
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let Ok(result) = subscriber.recv().await else {
 | 
				
			||||||
 | 
					        context
 | 
				
			||||||
 | 
					            .pubsub
 | 
				
			||||||
 | 
					            .publish(
 | 
				
			||||||
 | 
					                channels::MSG_RELAY,
 | 
				
			||||||
 | 
					                crate::bussy::Message {
 | 
				
			||||||
 | 
					                    source: channels::MSG_RELAY,
 | 
				
			||||||
 | 
					                    content: crate::bussy::Command::DbResOkWithStatus(client.client_id, message),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await;
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let crate::bussy::Command::DbResGetProfile(profile) = result.content {
 | 
				
			||||||
 | 
					        client.authenticate(context.config.get_relay_url(), &event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let client_status = format!("Client authenticated: {}", client.authenticated);
 | 
				
			||||||
 | 
					        let status_message = if client.authenticated {
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            "auth-required: we only accept events from registered users"
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        message = nostr::RelayMessage::ok(event.id, client.authenticated, status_message);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context
 | 
				
			||||||
 | 
					        .pubsub
 | 
				
			||||||
 | 
					        .publish(
 | 
				
			||||||
 | 
					            channels::MSG_RELAY,
 | 
				
			||||||
 | 
					            crate::bussy::Message {
 | 
				
			||||||
 | 
					                source: channels::MSG_RELAY,
 | 
				
			||||||
 | 
					                content: crate::bussy::Command::DbResOkWithStatus(client.client_id, message),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn unhandled_message(context: &Context, client: &Client) {
 | 
				
			||||||
 | 
					    let message = nostr::RelayMessage::notice("Unsupported Message");
 | 
				
			||||||
 | 
					    context
 | 
				
			||||||
 | 
					        .pubsub
 | 
				
			||||||
 | 
					        .publish(
 | 
				
			||||||
 | 
					            channels::MSG_RELAY,
 | 
				
			||||||
 | 
					            crate::bussy::Message {
 | 
				
			||||||
 | 
					                source: channels::MSG_RELAY,
 | 
				
			||||||
 | 
					                content: crate::bussy::Command::DbResOkWithStatus(client.client_id, message),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,7 @@
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use crate::usernames::validators::{validate_pubkey, validate_relays};
 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
				
			||||||
| 
						 | 
					@ -12,31 +10,26 @@ 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)]
 | 
					#[derive(Serialize, Deserialize, Debug, Validate, PartialEq, Clone)]
 | 
				
			||||||
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"))]
 | 
				
			||||||
    pub pubkey: String,
 | 
					    pubkey: String,
 | 
				
			||||||
 | 
					    #[validate(custom(function = "validate_relays"))]
 | 
				
			||||||
 | 
					    pub relays: Vec<String>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl UserBody {
 | 
					impl UserBody {
 | 
				
			||||||
    pub fn get_pubkey(&self) -> XOnlyPublicKey {
 | 
					    pub fn get_pubkey(&self) -> String {
 | 
				
			||||||
        let keys = Keys::from_pk_str(&self.pubkey).unwrap();
 | 
					        nostr::Keys::from_pk_str(&self.pubkey).unwrap().public_key().to_string()
 | 
				
			||||||
 | 
					 | 
				
			||||||
        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 user: String,
 | 
					    pub name: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize, Debug, Clone, Validate)]
 | 
					#[derive(Serialize, Deserialize, Debug, Clone, Validate)]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,6 @@ 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,6 +6,8 @@ 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,
 | 
				
			||||||
| 
						 | 
					@ -45,14 +47,9 @@ 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 user = User {
 | 
					    let command = Command::DbReqGetNIP05(user_query.name);
 | 
				
			||||||
        name: Some(name),
 | 
					 | 
				
			||||||
        pubkey: None,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    let command = Command::DbReqGetUser(user);
 | 
					 | 
				
			||||||
    context
 | 
					    context
 | 
				
			||||||
        .pubsub
 | 
					        .pubsub
 | 
				
			||||||
        .publish(
 | 
					        .publish(
 | 
				
			||||||
| 
						 | 
					@ -65,18 +62,44 @@ 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::DbResUser(user) => {
 | 
					            Command::DbResNIP05(profile) => {
 | 
				
			||||||
                response = json!({
 | 
					                let response = serde_json::to_value(profile).unwrap();
 | 
				
			||||||
                    "names": {
 | 
					 | 
				
			||||||
                        user.username: user.pubkey
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    "relays": {}
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                Ok(warp::reply::json(&response))
 | 
					                Ok(warp::reply::json(&response))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Command::ServiceError(e) => Ok(warp::reply::json(&response)),
 | 
					            Command::ServiceError(e) => Err(warp::reject::custom(e)),
 | 
				
			||||||
 | 
					            _ => Err(warp::reject::custom(Error::internal_with_message(
 | 
				
			||||||
 | 
					                "Unhandeled message type",
 | 
				
			||||||
 | 
					            ))),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        Err(warp::reject::custom(Error::internal_with_message(
 | 
				
			||||||
 | 
					            "Unhandeled message type",
 | 
				
			||||||
 | 
					        )))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn create_user(user_body: UserBody, context: Context) -> Result<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,49 +1,69 @@
 | 
				
			||||||
use crate::noose::user::User;
 | 
					use crate::noose::user::User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::accounts::create_account;
 | 
					// use super::accounts::create_account;
 | 
				
			||||||
use super::dto::{Account, UserQuery};
 | 
					use super::dto::{Account, UserBody, UserQuery};
 | 
				
			||||||
use super::filter::{validate_body_filter, validate_query_filter};
 | 
					use super::filter::{validate_body_filter, validate_query_filter};
 | 
				
			||||||
use super::handler::{get_account, get_user};
 | 
					use super::handler::{create_user, 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(account_create(context.clone()))
 | 
					        .or(nip05_create(context.clone()))
 | 
				
			||||||
 | 
					        .with(&cors)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn nip05_get(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					fn well_known<M>(warp_method: M) -> impl Filter<Extract = (), Error = Rejection> + Clone
 | 
				
			||||||
    warp::get()
 | 
					where
 | 
				
			||||||
        .and(warp::path(".well-known"))
 | 
					    M: (Filter<Extract = (), Error = Rejection>) + Copy,
 | 
				
			||||||
        .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))
 | 
					        .and(with_context(context.clone()))
 | 
				
			||||||
        .and_then(get_user)
 | 
					        .and_then(get_user)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn account_create(
 | 
					fn nip05_create(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
    context: Context,
 | 
					    well_known(warp::post())
 | 
				
			||||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					        .and(warp::path("nostr.json"))
 | 
				
			||||||
    warp::path("account")
 | 
					        .and(warp::body::content_length_limit(1024))
 | 
				
			||||||
        .and(warp::post())
 | 
					        .and(validate_body_filter::<UserBody>())
 | 
				
			||||||
        .and(validate_body_filter::<User>())
 | 
					        .and(with_context(context.clone()))
 | 
				
			||||||
        .and(with_context(context))
 | 
					        .and_then(create_user)
 | 
				
			||||||
        .and_then(create_account)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn account_get(
 | 
					// pub fn account_create(
 | 
				
			||||||
    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::get())
 | 
					//         .and(warp::post())
 | 
				
			||||||
        .and(validate_body_filter::<Account>())
 | 
					//         .and(validate_body_filter::<User>())
 | 
				
			||||||
        .and(with_context(context))
 | 
					//         .and(with_context(context))
 | 
				
			||||||
        .and_then(get_account)
 | 
					//         .and_then(create_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};
 | 
					use validator::{Validate, ValidationError, validate_url};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn validate_account_pubkey_query(
 | 
					pub async fn validate_account_pubkey_query(
 | 
				
			||||||
    account_pubkey: AccountPubkey,
 | 
					    account_pubkey: AccountPubkey,
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,18 @@ 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("Unable to parse pk_str")),
 | 
					        Err(_) => Err(ValidationError::new("Failed to parse pubkey")),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
					use std::{path::PathBuf, str::FromStr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey};
 | 
					use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,8 @@ 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 {
 | 
				
			||||||
| 
						 | 
					@ -23,13 +25,24 @@ 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
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -38,14 +51,18 @@ 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, 9, 11, 12, 15, 16, 20, 22, 28, 33, 45 ],
 | 
					            "supported_nips": [ 1, 2, 4, 9, 11, 12, 15, 16, 20, 22, 28, 33, 40, 42, 45, 50 ],
 | 
				
			||||||
            "software": "git+https://git.zhitno.st/Klink/sneedstr.git",
 | 
					            "software": "git+https://git.zhitno.st/Klink/sneedstr.git",
 | 
				
			||||||
            "version": "0.1.0"
 | 
					            "version": "0.1.1"
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,
 | 
				
			||||||
    /// Sneedstr version.
 | 
					    pub sneedstr_version: String,
 | 
				
			||||||
    #[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: None,
 | 
					            sneedstr_version: VERSION.to_string(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,12 +54,11 @@ impl Error {
 | 
				
			||||||
        Self::new(StatusCode::BAD_REQUEST, message)
 | 
					        Self::new(StatusCode::BAD_REQUEST, message)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn not_found<S: Display>(resource: &str, identifier: S, service_version: u16) -> Self {
 | 
					    pub fn not_found<S: Display>(resource: &str, identifier: S) -> 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 {
 | 
				
			||||||
| 
						 | 
					@ -77,11 +76,6 @@ 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 {
 | 
				
			||||||
| 
						 | 
					@ -116,28 +110,36 @@ 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!(err.to_string(), "400 Bad Request: invalid address")
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            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!(err.to_string(), "500 Internal Server Error: hello")
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            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 =
 | 
					        let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned());
 | 
				
			||||||
            Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()).sneedstr_version(123);
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            err.to_string(),
 | 
					            err.to_string(),
 | 
				
			||||||
            "400 Bad Request: invalid address\ndiem ledger version: 123"
 | 
					            "Error { code: 400, message: 'invalid address', sneedstr_version: \"0.1.1\" }"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[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!(err.to_string(), "500 Internal Server Error: hello")
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            err.to_string(),
 | 
				
			||||||
 | 
					            "Error { code: 500, message: 'hello', sneedstr_version: \"0.1.1\" }"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ 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;
 | 
				
			||||||
| 
						 | 
					@ -50,6 +51,8 @@ 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,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,6 +63,8 @@ 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,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -68,6 +73,43 @@ 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();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,66 +0,0 @@
 | 
				
			||||||
<!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>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,70 +0,0 @@
 | 
				
			||||||
<!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>
 | 
					 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 43 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 235 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 39 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 777 B  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 2.2 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 15 KiB  | 
| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
{"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