Skip to content

Commit a7bf49f

Browse files
authored
Refactor/setup account (#310)
* refactor(accounts/relays): extract publish_relay_list_for_pubkey function from publish_relay_list_for_account * chore(justfile): add @just --list as default just command * refactor(accounts/mod): clean create_identity and login methods Removes setup_account and creates individual private functions for the required steps
1 parent aa07b2d commit a7bf49f

File tree

3 files changed

+331
-164
lines changed

3 files changed

+331
-164
lines changed

justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
# Development
33
######################
44

5+
# Default recipe - show available commands
6+
default:
7+
@just --list
8+
59
# Clear local data dirs (ensure you're not running docker compose before running)
610
clear-dev-data:
711
rm -rf ./dev/data/*

src/whitenoise/accounts/mod.rs

Lines changed: 137 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -252,29 +252,20 @@ impl Whitenoise {
252252
///
253253
/// Returns a [`WhitenoiseError`] if any step fails. The operation is atomic with cleanup on failure.
254254
pub async fn create_identity(&self) -> Result<Account> {
255-
// Step 1: Generate a new keypair
256255
let keys = Keys::generate();
257256
tracing::debug!(target: "whitenoise::create_identity", "Generated new keypair: {}", keys.public_key().to_hex());
258257

259-
// Step 2: Setup the account (this handles all the common logic)
260-
let account = self.setup_account(&keys, true).await?;
258+
let mut account = self.create_base_account_with_private_key(&keys)?;
259+
tracing::debug!(target: "whitenoise::create_identity", "Keys stored in secret store");
261260

262-
// Step 3: For new accounts only, create and publish metadata with petname
263-
let petname = petname::petname(2, " ")
264-
.unwrap_or_else(|| "Anonymous User".to_string())
265-
.split_whitespace()
266-
.map(Whitenoise::capitalize_first_letter)
267-
.collect::<Vec<_>>()
268-
.join(" ");
261+
self.setup_relays_for_new_account(&mut account).await?;
262+
tracing::debug!(target: "whitenoise::create_identity", "Relays setup");
269263

270-
let metadata = Metadata {
271-
name: Some(petname.clone()),
272-
display_name: Some(petname),
273-
..Default::default()
274-
};
264+
self.persist_and_activate_account(&account).await?;
265+
tracing::debug!(target: "whitenoise::create_identity", "Account persisted and activated");
275266

276-
self.update_metadata(&metadata, &account).await?;
277-
tracing::debug!(target: "whitenoise::create_identity", "Created and published metadata with petname: {}", metadata.name.as_ref().unwrap_or(&"Unknown".to_string()));
267+
self.setup_metadata(&account).await?;
268+
tracing::debug!(target: "whitenoise::create_identity", "Metadata setup");
278269

279270
tracing::debug!(target: "whitenoise::create_identity", "Successfully created new identity: {}", account.pubkey.to_hex());
280271
Ok(account)
@@ -302,7 +293,17 @@ impl Whitenoise {
302293
let pubkey = keys.public_key();
303294
tracing::debug!(target: "whitenoise::login", "Logging in with pubkey: {}", pubkey.to_hex());
304295

305-
let account = self.setup_account(&keys, false).await?;
296+
let mut account = self.create_base_account_with_private_key(&keys)?;
297+
tracing::debug!(target: "whitenoise::login", "Keys stored in secret store");
298+
299+
self.setup_relays_for_existing_account(&mut account).await?;
300+
tracing::debug!(target: "whitenoise::login", "Relays setup");
301+
302+
self.persist_and_activate_account(&account).await?;
303+
tracing::debug!(target: "whitenoise::login", "Account persisted and activated");
304+
305+
self.background_fetch_account_data(&account).await?;
306+
tracing::debug!(target: "whitenoise::login", "Background data fetch triggered");
306307

307308
tracing::debug!(target: "whitenoise::login", "Successfully logged in: {}", account.pubkey.to_hex());
308309
Ok(account)
@@ -565,164 +566,152 @@ impl Whitenoise {
565566
})
566567
}
567568

568-
/// Sets up an account for use in Whitenoise (shared logic for both new and existing accounts).
569-
///
570-
/// This method handles the common setup logic for both new accounts (created via create_identity)
571-
/// and existing accounts (loaded via login). The process is atomic with automatic cleanup on failure.
572-
///
573-
/// The operation follows this sequence:
574-
/// 1. **Store private key** - Ensures the private key is saved to the system keychain/secret store
575-
/// 2. **Handle relay lists** - For existing accounts, fetches from network; for new accounts or missing lists, uses defaults
576-
/// 3. **Create/update account struct** - Builds the account with proper relay configuration
577-
/// 4. **Save account to database** - Persists the account record
578-
/// 5. **Publish relay lists** - Only publishes if we had to create default relay lists
579-
/// 6. **Setup account in memory** - Adds to in-memory accounts list and connects to relays
580-
/// 7. **Setup subscriptions** - Configures Nostr subscriptions for the account
581-
/// 8. **Handle key package** - Publishes a key package if none exists
582-
///
583-
/// # Arguments
584-
///
585-
/// * `keys` - The Nostr keypair for the account
586-
/// * `is_new_account` - Whether this is a newly created account (vs an existing one being loaded)
587-
///
588-
/// # Returns
589-
///
590-
/// Returns the fully configured `Account` ready for use.
591-
///
592-
/// # Errors
593-
///
594-
/// Returns a `WhitenoiseError` if any critical operation fails. On failure, partial state is cleaned up.
595-
/// TODO: Refactor this method to clean up on error and return a proper error state.
596-
async fn setup_account(&self, keys: &Keys, is_new_account: bool) -> Result<Account> {
597-
let pubkey = keys.public_key();
598-
tracing::debug!(target: "whitenoise::setup_account", "Setting up account for pubkey: {} (new: {})", pubkey.to_hex(), is_new_account);
599-
600-
// Step 1: Store private key first
601-
self.secrets_store.store_private_key(keys).map_err(|e| {
602-
tracing::error!(target: "whitenoise::setup_account", "Failed to store private key: {}", e);
603-
e
604-
})?;
605-
tracing::debug!(target: "whitenoise::setup_account", "Keys stored in secret store");
606-
607-
// Step 2: Handle relay lists - fetch for existing accounts, use defaults for new or missing
608-
// Track which relay types need to be published (defaulted to fallback values)
609-
let mut need_to_publish_nip65 = is_new_account; // Always publish for new accounts
610-
let mut need_to_publish_inbox = is_new_account;
611-
let mut need_to_publish_key_package = is_new_account;
612-
613-
let nip65_relays = if is_new_account {
614-
Account::default_relays()
615-
} else {
616-
match self
617-
.fetch_relays_from(Account::default_relays(), pubkey, RelayType::Nostr)
618-
.await
619-
{
620-
Ok(relays) if !relays.is_empty() => relays,
621-
_ => {
622-
need_to_publish_nip65 = true;
623-
Account::default_relays()
624-
}
625-
}
626-
};
569+
async fn persist_and_activate_account(&self, account: &Account) -> Result<()> {
570+
self.persist_account(account).await?;
571+
tracing::debug!(target: "whitenoise::persist_and_activate_account", "Account saved to database");
572+
self.connect_account_relays(account).await?;
573+
tracing::debug!(target: "whitenoise::persist_and_activate_account", "Relays connected");
574+
self.setup_subscriptions(account).await?;
575+
tracing::debug!(target: "whitenoise::persist_and_activate_account", "Subscriptions setup");
576+
self.setup_key_package(account).await?;
577+
tracing::debug!(target: "whitenoise::persist_and_activate_account", "Key package setup");
578+
Ok(())
579+
}
627580

628-
let inbox_relays = if is_new_account {
629-
Account::default_relays()
630-
} else {
631-
match self
632-
.fetch_relays_from(nip65_relays.clone(), pubkey, RelayType::Inbox)
633-
.await
634-
{
635-
Ok(relays) if !relays.is_empty() => relays,
636-
_ => {
637-
need_to_publish_inbox = true;
638-
Account::default_relays()
639-
}
640-
}
641-
};
581+
async fn setup_metadata(&self, account: &Account) -> Result<()> {
582+
let petname = petname::petname(2, " ")
583+
.unwrap_or_else(|| "Anonymous User".to_string())
584+
.split_whitespace()
585+
.map(Whitenoise::capitalize_first_letter)
586+
.collect::<Vec<_>>()
587+
.join(" ");
642588

643-
let key_package_relays = if is_new_account {
644-
Account::default_relays()
645-
} else {
646-
match self
647-
.fetch_relays_from(nip65_relays.clone(), pubkey, RelayType::KeyPackage)
648-
.await
649-
{
650-
Ok(relays) if !relays.is_empty() => relays,
651-
_ => {
652-
need_to_publish_key_package = true;
653-
Account::default_relays()
654-
}
655-
}
589+
let metadata = Metadata {
590+
name: Some(petname.clone()),
591+
display_name: Some(petname),
592+
..Default::default()
656593
};
657594

658-
// Step 3: Create account struct
659-
let account = Account {
660-
pubkey,
661-
settings: AccountSettings::default(),
662-
last_synced: Timestamp::zero(),
663-
nip65_relays,
664-
inbox_relays,
665-
key_package_relays,
666-
nostr_mls: Account::create_nostr_mls(pubkey, &self.config.data_dir)?,
667-
};
595+
self.update_metadata(&metadata, account).await?;
596+
tracing::debug!(target: "whitenoise::setup_metadata", "Created and published metadata with petname: {}", metadata.name.as_ref().unwrap_or(&"Unknown".to_string()));
597+
Ok(())
598+
}
668599

669-
// Step 4: Save account to database
670-
self.save_account(&account).await.map_err(|e| {
600+
async fn persist_account(&self, account: &Account) -> Result<()> {
601+
self.save_account(account).await.map_err(|e| {
671602
tracing::error!(target: "whitenoise::setup_account", "Failed to save account: {}", e);
672603
// Try to clean up stored private key
673-
if let Err(cleanup_err) = self.secrets_store.remove_private_key_for_pubkey(&pubkey) {
604+
if let Err(cleanup_err) = self.secrets_store.remove_private_key_for_pubkey(&account.pubkey) {
674605
tracing::error!(target: "whitenoise::setup_account", "Failed to cleanup private key after account save failure: {}", cleanup_err);
675606
}
676607
e
677608
})?;
678609
tracing::debug!(target: "whitenoise::setup_account", "Account saved to database");
679610

680-
// Step 5: Publish relay lists if we created defaults (only publish what defaulted)
681-
if need_to_publish_nip65 {
682-
self.publish_relay_list_for_account(&account, RelayType::Nostr, &None)
683-
.await?;
684-
}
685-
if need_to_publish_inbox {
686-
self.publish_relay_list_for_account(&account, RelayType::Inbox, &None)
687-
.await?;
688-
}
689-
if need_to_publish_key_package {
690-
self.publish_relay_list_for_account(&account, RelayType::KeyPackage, &None)
691-
.await?;
692-
}
693-
tracing::debug!(target: "whitenoise::setup_account", "Published relay lists for defaulted types");
694-
695-
// Step 6: Setup account in memory and connect to relays
696-
self.connect_account_relays(&account).await?;
697611
{
698612
let mut accounts = self.write_accounts().await;
699613
accounts.insert(account.pubkey, account.clone());
700614
}
701-
tracing::debug!(target: "whitenoise::setup_account", "Account added to memory and relays connected");
702-
703-
// Step 7: Setup subscriptions
704-
self.setup_subscriptions(&account).await?;
705-
tracing::debug!(target: "whitenoise::setup_account", "Subscriptions setup");
615+
tracing::debug!(target: "whitenoise::setup_account", "Account added to memory");
616+
Ok(())
617+
}
706618

707-
// Step 8: Handle key package - publish if none exists
619+
async fn setup_key_package(&self, account: &Account) -> Result<()> {
708620
let key_package_event = self
709-
.fetch_key_package_event_from(account.key_package_relays.clone(), pubkey)
621+
.fetch_key_package_event_from(account.key_package_relays.clone(), account.pubkey)
710622
.await?;
711623
if key_package_event.is_none() {
712-
self.publish_key_package_for_account(&account).await?;
624+
self.publish_key_package_for_account(account).await?;
713625
tracing::debug!(target: "whitenoise::setup_account", "Published key package");
714626
}
627+
Ok(())
628+
}
715629

716-
// For existing accounts, trigger background data fetch
717-
if !is_new_account {
718-
self.background_fetch_account_data(&account).await?;
719-
tracing::debug!(target: "whitenoise::setup_account", "Background data fetch triggered");
720-
}
630+
fn create_base_account_with_private_key(&self, keys: &Keys) -> Result<Account> {
631+
let pubkey = keys.public_key();
632+
let account = Account {
633+
pubkey,
634+
settings: AccountSettings::default(),
635+
last_synced: Timestamp::zero(),
636+
nip65_relays: DashSet::new(),
637+
inbox_relays: DashSet::new(),
638+
key_package_relays: DashSet::new(),
639+
nostr_mls: Account::create_nostr_mls(pubkey, &self.config.data_dir)?,
640+
};
641+
642+
self.secrets_store.store_private_key(keys).map_err(|e| {
643+
tracing::error!(target: "whitenoise::setup_account", "Failed to store private key: {}", e);
644+
e
645+
})?;
721646

722-
tracing::debug!(target: "whitenoise::setup_account", "Account setup completed successfully");
723647
Ok(account)
724648
}
725649

650+
async fn setup_relays_for_existing_account(&self, account: &mut Account) -> Result<()> {
651+
let pubkey = account.pubkey;
652+
account.nip65_relays = self
653+
.fetch_or_publish_default_relays(pubkey, RelayType::Nostr, Account::default_relays())
654+
.await?;
655+
account.inbox_relays = self
656+
.fetch_or_publish_default_relays(pubkey, RelayType::Inbox, account.nip65_relays.clone())
657+
.await?;
658+
account.key_package_relays = self
659+
.fetch_or_publish_default_relays(
660+
pubkey,
661+
RelayType::KeyPackage,
662+
account.nip65_relays.clone(),
663+
)
664+
.await?;
665+
666+
Ok(())
667+
}
668+
669+
async fn fetch_or_publish_default_relays(
670+
&self,
671+
pubkey: PublicKey,
672+
relay_type: RelayType,
673+
source_relays: DashSet<RelayUrl>,
674+
) -> Result<DashSet<RelayUrl>> {
675+
match self
676+
.fetch_relays_from(source_relays.clone(), pubkey, relay_type)
677+
.await
678+
{
679+
Ok(relays) if !relays.is_empty() => Ok(relays),
680+
_ => {
681+
let default_relays = Account::default_relays();
682+
self.publish_relay_list_for_pubkey(
683+
pubkey,
684+
default_relays.clone(),
685+
relay_type,
686+
source_relays,
687+
)
688+
.await?;
689+
Ok(default_relays)
690+
}
691+
}
692+
}
693+
694+
async fn setup_relays_for_new_account(&self, account: &mut Account) -> Result<()> {
695+
let default_relays = Account::default_relays();
696+
697+
// New accounts use default relays for all relay types
698+
for relay_type in [RelayType::Nostr, RelayType::Inbox, RelayType::KeyPackage] {
699+
self.publish_relay_list_for_pubkey(
700+
account.pubkey,
701+
default_relays.clone(),
702+
relay_type,
703+
default_relays.clone(),
704+
)
705+
.await?;
706+
}
707+
708+
account.nip65_relays = default_relays.clone();
709+
account.inbox_relays = default_relays.clone();
710+
account.key_package_relays = default_relays;
711+
712+
Ok(())
713+
}
714+
726715
/// Saves the provided `Account` to the database.
727716
///
728717
/// This method inserts or updates the account record in the database, serializing all

0 commit comments

Comments
 (0)