persistence.rs (6306B)
1 use std::sync::Arc; 2 3 use harvestcircle_application::{ 4 AppCore, AppSnapshot, Clock, DurableRequestId, GenerateIdentityReceipt, ImportIdentityReceipt, 5 KeyMaterialProvider, RelayConfiguration, RemovalConfirmationToken, SecretStore, 6 StagedGeneratedKey, 7 }; 8 use harvestcircle_domain::{PublicKey, SafeError, SecretKeyInput}; 9 use harvestcircle_nostr::NostrKeyMaterialProvider; 10 use harvestcircle_storage::Database; 11 use radroots_runtime_paths::RuntimeContext; 12 use radroots_service_sqlite::MigrationBuildIdentity; 13 14 use crate::{InstallationIdentity, InstallationIdentitySource}; 15 16 pub struct PersistentAppCore { 17 core: AppCore, 18 database: Database, 19 key_material: Arc<dyn KeyMaterialProvider>, 20 } 21 22 impl PersistentAppCore { 23 pub(crate) async fn initialize_installation_identity( 24 &self, 25 source: &dyn InstallationIdentitySource, 26 ) -> Result<InstallationIdentity, SafeError> { 27 if let Some(existing) = self.database.load_installation_id().await? { 28 return InstallationIdentity::parse(existing); 29 } 30 let candidate = source.generate()?; 31 InstallationIdentity::parse( 32 self.database 33 .initialize_installation_id(candidate.as_str()) 34 .await?, 35 ) 36 } 37 38 /// Opens the canonical application database without accessing credentials or relays. 39 pub async fn open( 40 context: &RuntimeContext, 41 relay_configuration: RelayConfiguration, 42 created_at_unix_ms: u64, 43 applied_at_unix_s: u64, 44 build: &MigrationBuildIdentity, 45 ) -> Result<Self, SafeError> { 46 let key_material: Arc<dyn KeyMaterialProvider> = Arc::new(NostrKeyMaterialProvider); 47 Ok(Self { 48 core: AppCore::new(relay_configuration, Arc::clone(&key_material)), 49 database: Database::open(context, created_at_unix_ms, applied_at_unix_s, build).await?, 50 key_material, 51 }) 52 } 53 54 pub(crate) fn key_material(&self) -> &dyn KeyMaterialProvider { 55 self.key_material.as_ref() 56 } 57 58 /// Reconciles the UUID ledger and restores public state without activating a session. 59 pub async fn bootstrap( 60 &self, 61 secrets: &(impl SecretStore + ?Sized), 62 clock: &(impl Clock + ?Sized), 63 ) -> Result<AppSnapshot, SafeError> { 64 self.core 65 .recover_durable_operations( 66 &self.database, 67 &self.database, 68 secrets, 69 &self.database, 70 clock, 71 ) 72 .await?; 73 self.core 74 .bootstrap_from(&self.database, &self.database) 75 .await 76 } 77 78 pub async fn commit_staged_generated_key( 79 &self, 80 request_id: &DurableRequestId, 81 staged: StagedGeneratedKey, 82 secrets: &(impl SecretStore + ?Sized), 83 clock: &(impl Clock + ?Sized), 84 ) -> Result<ImportIdentityReceipt, SafeError> { 85 self.core 86 .commit_staged_generated_key( 87 request_id, 88 staged, 89 &self.database, 90 &self.database, 91 secrets, 92 &self.database, 93 clock, 94 ) 95 .await 96 } 97 98 pub async fn generate_identity_durable( 99 &self, 100 request_id: &DurableRequestId, 101 expected_revision: u64, 102 secrets: &(impl SecretStore + ?Sized), 103 clock: &(impl Clock + ?Sized), 104 ) -> Result<GenerateIdentityReceipt, SafeError> { 105 self.core 106 .generate_identity_durable( 107 request_id, 108 expected_revision, 109 &self.database, 110 &self.database, 111 secrets, 112 &self.database, 113 clock, 114 ) 115 .await 116 } 117 118 pub async fn import_secret_key_durable( 119 &self, 120 request_id: &DurableRequestId, 121 expected_revision: u64, 122 input: SecretKeyInput, 123 secrets: &(impl SecretStore + ?Sized), 124 clock: &(impl Clock + ?Sized), 125 ) -> Result<ImportIdentityReceipt, SafeError> { 126 self.core 127 .import_secret_key_durable( 128 request_id, 129 expected_revision, 130 input, 131 &self.database, 132 &self.database, 133 secrets, 134 &self.database, 135 clock, 136 ) 137 .await 138 } 139 140 pub async fn select_identity(&self, public_key: PublicKey) -> Result<AppSnapshot, SafeError> { 141 self.core 142 .select_identity(public_key, &self.database, &self.database) 143 .await 144 } 145 146 pub async fn activate_identity( 147 &self, 148 public_key: PublicKey, 149 secrets: &(impl SecretStore + ?Sized), 150 clock: &(impl Clock + ?Sized), 151 ) -> Result<AppSnapshot, SafeError> { 152 self.core 153 .activate_identity( 154 public_key, 155 &self.database, 156 &self.database, 157 &self.database, 158 secrets, 159 clock, 160 ) 161 .await 162 } 163 164 pub fn sign_out(&self) -> Result<AppSnapshot, SafeError> { 165 self.core.sign_out() 166 } 167 168 pub fn request_identity_removal( 169 &self, 170 public_key: PublicKey, 171 clock: &(impl Clock + ?Sized), 172 ) -> Result<RemovalConfirmationToken, SafeError> { 173 self.core.request_identity_removal(public_key, clock) 174 } 175 176 pub async fn confirm_identity_removal_durable( 177 &self, 178 request_id: &DurableRequestId, 179 token: RemovalConfirmationToken, 180 secrets: &(impl SecretStore + ?Sized), 181 clock: &(impl Clock + ?Sized), 182 ) -> Result<AppSnapshot, SafeError> { 183 self.core 184 .confirm_identity_removal_durable( 185 request_id, 186 token, 187 &self.database, 188 &self.database, 189 secrets, 190 &self.database, 191 clock, 192 ) 193 .await 194 } 195 196 pub async fn close(&self) -> Result<(), SafeError> { 197 self.database.close().await 198 } 199 200 #[must_use] 201 pub const fn core(&self) -> &AppCore { 202 &self.core 203 } 204 205 #[must_use] 206 pub const fn database(&self) -> &Database { 207 &self.database 208 } 209 }