app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

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 }