Implemented feature:
- NIP-42 Can be enabled in nix module or as environment variable 'CONFIG_ENABLE_AUTH' - NIP-05 Still WIP, but building up slowly
This commit is contained in:
		
							parent
							
								
									377da44eed
								
							
						
					
					
						commit
						f7b74bd22c
					
				
					 18 changed files with 1001 additions and 294 deletions
				
			
		| 
						 | 
					@ -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/
 | 
					 | 
				
			||||||
          '';
 | 
					          '';
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,11 @@ in {
 | 
				
			||||||
      'npub' of the administrator account. Must be defined!
 | 
					      'npub' of the administrator account. Must be defined!
 | 
				
			||||||
      '';
 | 
					      '';
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    enableAuth = mkOption {
 | 
				
			||||||
 | 
					      type = type.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;
 | 
				
			||||||
| 
						 | 
					@ -64,6 +69,7 @@ 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 = cfg.enableAuth;
 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
          startLimitBurst = 1;
 | 
					          startLimitBurst = 1;
 | 
				
			||||||
          startLimitIntervalSec = 10;
 | 
					          startLimitIntervalSec = 10;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,10 @@
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    noose::sled::BanInfo,
 | 
					    noose::sled::BanInfo,
 | 
				
			||||||
    noose::user::{User, UserRow, Nip05Profile},
 | 
					    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 {
 | 
				
			||||||
| 
						 | 
					@ -18,22 +18,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),
 | 
				
			||||||
    DbReqCreateAccount(XOnlyPublicKey, String, String),
 | 
					    DbReqCreateAccount(XOnlyPublicKey, String, String),
 | 
				
			||||||
    DbReqGetAccount(String),
 | 
					    DbReqGetAccount(String),
 | 
				
			||||||
    DbReqClear,
 | 
					    DbReqClear,
 | 
				
			||||||
    // NIP-05 related messages
 | 
					    // NIP-05 related messages
 | 
				
			||||||
    DbReqGetUser(String),
 | 
					    DbReqGetNIP05(String),
 | 
				
			||||||
    DbResUser(Nip05Profile),
 | 
					    DbReqCreateNIP05(UserBody),
 | 
				
			||||||
 | 
					    // --- Res
 | 
				
			||||||
    
 | 
					    DbResNIP05(Nip05Profile),
 | 
				
			||||||
    // DbResponse
 | 
					 | 
				
			||||||
    DbResRelayMessages(
 | 
					    DbResRelayMessages(
 | 
				
			||||||
        /* client_id*/ uuid::Uuid,
 | 
					        /* client_id*/ uuid::Uuid,
 | 
				
			||||||
        /* Vec<RelayMessage::Event> */ Vec<nostr::RelayMessage>,
 | 
					        /* Vec<RelayMessage::Event> */ Vec<nostr::RelayMessage>,
 | 
				
			||||||
| 
						 | 
					@ -43,24 +42,47 @@ pub enum Command {
 | 
				
			||||||
    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
 | 
				
			||||||
    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,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +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;
 | 
					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>;
 | 
					    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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ impl MigrationRunner {
 | 
				
			||||||
        let m_users = include_str!("./1697410294265_users.sql");
 | 
					        let m_users = include_str!("./1697410294265_users.sql");
 | 
				
			||||||
        let m_unattached_media = include_str!("./1697410480767_unattached_media.sql");
 | 
					        let m_unattached_media = include_str!("./1697410480767_unattached_media.sql");
 | 
				
			||||||
        let m_nip05 = include_str!("./1706575155557_nip05.sql");
 | 
					        let m_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),
 | 
				
			||||||
| 
						 | 
					@ -21,6 +22,7 @@ impl MigrationRunner {
 | 
				
			||||||
            M::up(m_users),
 | 
					            M::up(m_users),
 | 
				
			||||||
            M::up(m_unattached_media),
 | 
					            M::up(m_unattached_media),
 | 
				
			||||||
            M::up(m_nip05),
 | 
					            M::up(m_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) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,176 +5,23 @@ use super::{
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
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 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 {
 | 
				
			||||||
| 
						 | 
					@ -494,86 +341,159 @@ impl NostrSqlite {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        return Ok(false);
 | 
					                        return Ok(false);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    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()) {
 | 
					                    match tx.commit() {
 | 
				
			||||||
                        log::error!("Error inserting event into 'events' table: {}", err);
 | 
					                        Ok(_) => return Ok(true),
 | 
				
			||||||
 | 
					                        Err(err) => {
 | 
				
			||||||
 | 
					                            log::error!("Error during transaction commit: {}", err);
 | 
				
			||||||
 | 
					                            return Ok(false);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if event.kind() == nostr::Kind::Metadata {
 | 
				
			||||||
 | 
					                    // Try to delete old profile first
 | 
				
			||||||
 | 
					                    let (sql, value) = Query::delete()
 | 
				
			||||||
 | 
					                        .from_table(ProfilesTable::Table)
 | 
				
			||||||
 | 
					                        .and_where(sea_query::Expr::col(ProfilesTable::Pubkey).eq(pubkey.clone()))
 | 
				
			||||||
 | 
					                        .build_rusqlite(SqliteQueryBuilder);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if let Err(err) = tx.execute(sql.as_str(), &*value.as_params()) {
 | 
				
			||||||
                        tx.rollback().unwrap();
 | 
					                        tx.rollback().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        log::debug!("Failed to delete old Profile record: {}", err);
 | 
				
			||||||
                        return Ok(false);
 | 
					                        return Ok(false);
 | 
				
			||||||
                    };
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
                    // Insert into EventsFTS table
 | 
					                    let (sql, value) = Query::insert()
 | 
				
			||||||
                    log::debug!("inserting new event into eventsFTS");
 | 
					                        .into_table(ProfilesTable::Table)
 | 
				
			||||||
                    let (sql, values) = Query::insert()
 | 
					                        .columns([ProfilesTable::Pubkey])
 | 
				
			||||||
                        .into_table(EventsFTSTable::Table)
 | 
					                        .values_panic([pubkey.clone().into()])
 | 
				
			||||||
                        .columns([EventsFTSTable::EventId, EventsFTSTable::Content])
 | 
					 | 
				
			||||||
                        .values_panic([id.clone().into(), content.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 'eventsFTS' table: {}", err);
 | 
					 | 
				
			||||||
                        tx.rollback().unwrap();
 | 
					                        tx.rollback().unwrap();
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        log::debug!("Failed to store Profile");
 | 
				
			||||||
                        return Ok(false);
 | 
					                        return Ok(false);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    // Insert into Tags table
 | 
					                    let Ok(metadata) = nostr::Metadata::from_json(content.clone()) else {
 | 
				
			||||||
                    log::debug!("inserting new event into tags");
 | 
					                        log::debug!("Failed to parse metadata");
 | 
				
			||||||
                    for tag in event.tags.clone() {
 | 
					                        return Err(Error::bad_request(
 | 
				
			||||||
                        if Self::tag_is_indexable(&tag) {
 | 
					                            "Unable to parse metadata from 'content'",
 | 
				
			||||||
                            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())
 | 
					                    let metadata_custom_fields = serde_json::to_string(&metadata.custom);
 | 
				
			||||||
                                    {
 | 
					 | 
				
			||||||
                                        log::error!(
 | 
					 | 
				
			||||||
                                            "Error inserting event into 'tags' table: {}",
 | 
					 | 
				
			||||||
                                            err
 | 
					 | 
				
			||||||
                                        );
 | 
					 | 
				
			||||||
                                        tx.rollback().unwrap();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                        return Ok(false);
 | 
					                    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);
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
| 
						 | 
					@ -1265,6 +1185,105 @@ impl NostrSqlite {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        query_result
 | 
					        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 {
 | 
				
			||||||
| 
						 | 
					@ -1417,10 +1436,43 @@ impl Noose for NostrSqlite {
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                // NIP-05
 | 
					                // NIP-05
 | 
				
			||||||
                Command::DbReqGetUser(username) => match self.get_nip05(username).await {
 | 
					                Command::DbReqGetNIP05(username) => match self.get_nip05(username).await {
 | 
				
			||||||
                    Ok(user) => Command::DbResUser(user),
 | 
					                    Ok(user) => Command::DbResNIP05(user),
 | 
				
			||||||
                    Err(e) => Command::ServiceError(e),
 | 
					                    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 {
 | 
				
			||||||
| 
						 | 
					@ -1489,11 +1541,53 @@ impl Noose for NostrSqlite {
 | 
				
			||||||
    async fn get_nip05(&self, username: String) -> Result<Nip05Profile, Error> {
 | 
					    async fn get_nip05(&self, username: String) -> Result<Nip05Profile, Error> {
 | 
				
			||||||
        self.get_nip05_profile(username).await
 | 
					        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]
 | 
				
			||||||
| 
						 | 
					@ -1718,6 +1812,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());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										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)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								src/relay/ws.rs
									
										
									
									
									
								
							
							
						
						
									
										107
									
								
								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,
 | 
				
			||||||
| 
						 | 
					@ -213,21 +217,63 @@ fn send(client: &Client, message: Message) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 context.config.auth_required()
 | 
				
			||||||
 | 
					                && event.kind() != nostr::Kind::Metadata
 | 
				
			||||||
 | 
					                && !client.authenticated
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                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 context.config.auth_required() && !client.authenticated {
 | 
				
			||||||
 | 
					                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 context.config.auth_required() && !client.authenticated {
 | 
				
			||||||
 | 
					                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 +374,35 @@ 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");
 | 
					    client.authenticate(&event);
 | 
				
			||||||
    send(client, message);
 | 
					    let client_status = format!("Client authenticated: {}", client.authenticated);
 | 
				
			||||||
 | 
					    let message = nostr::RelayMessage::notice(client_status);
 | 
				
			||||||
 | 
					    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,27 +10,22 @@ 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))]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,
 | 
				
			||||||
| 
						 | 
					@ -47,7 +49,7 @@ 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 mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await;
 | 
					    let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let command = Command::DbReqGetUser(user_query.name);
 | 
					    let command = Command::DbReqGetNIP05(user_query.name);
 | 
				
			||||||
    context
 | 
					    context
 | 
				
			||||||
        .pubsub
 | 
					        .pubsub
 | 
				
			||||||
        .publish(
 | 
					        .publish(
 | 
				
			||||||
| 
						 | 
					@ -61,7 +63,7 @@ pub async fn get_user(user_query: UserQuery, context: Context) -> Result<impl Re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Ok(message) = subscriber.recv().await {
 | 
					    if let Ok(message) = subscriber.recv().await {
 | 
				
			||||||
        match message.content {
 | 
					        match message.content {
 | 
				
			||||||
            Command::DbResUser(profile) => {
 | 
					            Command::DbResNIP05(profile) => {
 | 
				
			||||||
                let response = serde_json::to_value(profile).unwrap();
 | 
					                let response = serde_json::to_value(profile).unwrap();
 | 
				
			||||||
                Ok(warp::reply::json(&response))
 | 
					                Ok(warp::reply::json(&response))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -76,3 +78,35 @@ pub async fn get_user(user_query: UserQuery, context: Context) -> Result<impl Re
 | 
				
			||||||
        )))
 | 
					        )))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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(
 | 
				
			||||||
 | 
					                "Unhandeled message type",
 | 
				
			||||||
 | 
					            ))),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        Err(warp::reject::custom(Error::internal_with_message(
 | 
				
			||||||
 | 
					            "Unhandeled message type",
 | 
				
			||||||
 | 
					        )))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
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};
 | 
				
			||||||
| 
						 | 
					@ -14,25 +14,37 @@ pub fn routes(context: Context) -> impl Filter<Extract = impl Reply, Error = Rej
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    index
 | 
					    index
 | 
				
			||||||
        .or(nip05_get(context.clone()))
 | 
					        .or(nip05_get(context.clone()))
 | 
				
			||||||
        // .or(account_create(context.clone()))
 | 
					        .or(nip05_create(context.clone()))
 | 
				
			||||||
        .with(&cors)
 | 
					        .with(&cors)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn well_known() -> impl Filter<Extract = (), Error = Rejection> + Clone {
 | 
					fn well_known<M>(warp_method: M) -> impl Filter<Extract = (), Error = Rejection> + Clone
 | 
				
			||||||
    warp::get().and(warp::path(".well-known"))
 | 
					where
 | 
				
			||||||
 | 
					    M: (Filter<Extract = (), Error = Rejection>) + Copy,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    warp_method.and(warp::path(".well-known"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn nostr_well_known() -> impl Filter<Extract = (), Error = Rejection> + Clone {
 | 
					fn nostr_well_known() -> impl Filter<Extract = (), Error = Rejection> + Clone {
 | 
				
			||||||
    well_known().and(warp::path("nostr.json"))
 | 
					    well_known(warp::get()).and(warp::path("nostr.json"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn nip05_get(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					fn nip05_get(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
    nostr_well_known()
 | 
					    nostr_well_known()
 | 
				
			||||||
        .and(validate_query_filter::<UserQuery>())
 | 
					        .and(validate_query_filter::<UserQuery>())
 | 
				
			||||||
        .and(with_context(context.clone()))
 | 
					        .and(with_context(context.clone()))
 | 
				
			||||||
        .and_then(get_user)
 | 
					        .and_then(get_user)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn nip05_create(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
 | 
					    well_known(warp::post())
 | 
				
			||||||
 | 
					        .and(warp::path("nostr.json"))
 | 
				
			||||||
 | 
					        .and(warp::body::content_length_limit(1024))
 | 
				
			||||||
 | 
					        .and(validate_body_filter::<UserBody>())
 | 
				
			||||||
 | 
					        .and(with_context(context.clone()))
 | 
				
			||||||
 | 
					        .and_then(create_user)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// pub fn account_create(
 | 
					// pub fn account_create(
 | 
				
			||||||
//     context: Context,
 | 
					//     context: Context,
 | 
				
			||||||
// ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
					// ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ 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,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for Config {
 | 
					impl Default for Config {
 | 
				
			||||||
| 
						 | 
					@ -23,13 +24,22 @@ 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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            admin_pubkey,
 | 
					            admin_pubkey,
 | 
				
			||||||
            db_path,
 | 
					            db_path,
 | 
				
			||||||
 | 
					            auth_required,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -43,7 +53,7 @@ impl Config {
 | 
				
			||||||
            "contact": "klink@zhitno.st",
 | 
					            "contact": "klink@zhitno.st",
 | 
				
			||||||
            "name": "zhitno.st",
 | 
					            "name": "zhitno.st",
 | 
				
			||||||
            "description": "Very *special* nostr relay",
 | 
					            "description": "Very *special* nostr relay",
 | 
				
			||||||
            "supported_nips": [ 1, 2, 9, 11, 12, 15, 16, 20, 22, 28, 33, 40, 45, 50 ],
 | 
					            "supported_nips": [ 1, 2, 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.1"
 | 
					            "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, 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(nostr::UncheckedUrl::from_str("ws://0.0.0.0:8080").unwrap()); // TODO: Use relay address from env variable
 | 
				
			||||||
 | 
					        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();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue