1#[cfg(feature = "redb")]
2pub mod lock;
3pub mod wallet;
4
5#[cfg(feature = "redb")]
6pub mod db;
7#[cfg(feature = "redb")]
8pub mod redb_store;
9#[cfg(feature = "redb")]
10pub mod transaction;
11
12#[cfg(feature = "postgres")]
13pub mod postgres_store;
14
15use crate::provider::PayError;
16use crate::types::{HistoryRecord, Network};
17use serde::Serialize;
18use std::collections::BTreeMap;
19use std::path::PathBuf;
20use wallet::WalletMetadata;
21
22#[derive(Debug, Clone, Serialize)]
23pub struct MigrationLog {
24 pub database: String,
25 pub from_version: u64,
26 pub to_version: u64,
27}
28
29#[allow(dead_code)]
31pub trait PayStore: Send + Sync {
32 fn save_wallet_metadata(&self, meta: &WalletMetadata) -> Result<(), PayError>;
34 fn load_wallet_metadata(&self, wallet_id: &str) -> Result<WalletMetadata, PayError>;
35 fn list_wallet_metadata(
36 &self,
37 network: Option<Network>,
38 ) -> Result<Vec<WalletMetadata>, PayError>;
39 fn delete_wallet_metadata(&self, wallet_id: &str) -> Result<(), PayError>;
40 fn wallet_directory_path(&self, wallet_id: &str) -> Result<PathBuf, PayError>;
41 fn wallet_data_directory_path(&self, wallet_id: &str) -> Result<PathBuf, PayError>;
42 fn wallet_data_directory_path_for_meta(&self, meta: &WalletMetadata) -> PathBuf;
43 fn resolve_wallet_id(&self, id_or_label: &str) -> Result<String, PayError>;
44
45 fn append_transaction_record(&self, record: &HistoryRecord) -> Result<(), PayError>;
47 fn load_wallet_transaction_records(
48 &self,
49 wallet_id: &str,
50 ) -> Result<Vec<HistoryRecord>, PayError>;
51 fn find_transaction_record_by_id(&self, tx_id: &str)
52 -> Result<Option<HistoryRecord>, PayError>;
53 fn update_transaction_record_memo(
54 &self,
55 tx_id: &str,
56 memo: Option<&BTreeMap<String, String>>,
57 ) -> Result<(), PayError>;
58 fn update_transaction_record_fee(
59 &self,
60 tx_id: &str,
61 fee_value: u64,
62 fee_unit: &str,
63 ) -> Result<(), PayError>;
64 fn update_transaction_record_status(
65 &self,
66 tx_id: &str,
67 status: crate::types::TxStatus,
68 confirmed_at_epoch_s: Option<u64>,
69 ) -> Result<(), PayError>;
70
71 fn drain_migration_log(&self) -> Vec<MigrationLog>;
73}
74
75#[derive(Clone)]
77pub enum StorageBackend {
78 #[cfg(feature = "redb")]
79 Redb(redb_store::RedbStore),
80 #[cfg(feature = "postgres")]
81 Postgres(postgres_store::PostgresStore),
82 #[doc(hidden)]
85 _None(std::convert::Infallible),
86}
87
88macro_rules! dispatch_storage {
90 ($self:expr, $method:ident $(, $arg:expr)*) => {
91 match $self {
92 #[cfg(feature = "redb")]
93 Self::Redb(s) => s.$method($($arg),*),
94 #[cfg(feature = "postgres")]
95 Self::Postgres(s) => s.$method($($arg),*),
96 Self::_None(n) => match *n {},
97 }
98 }
99}
100
101impl PayStore for StorageBackend {
102 fn save_wallet_metadata(&self, meta: &WalletMetadata) -> Result<(), PayError> {
103 dispatch_storage!(self, save_wallet_metadata, meta)
104 }
105
106 fn load_wallet_metadata(&self, wallet_id: &str) -> Result<WalletMetadata, PayError> {
107 dispatch_storage!(self, load_wallet_metadata, wallet_id)
108 }
109
110 fn list_wallet_metadata(
111 &self,
112 network: Option<Network>,
113 ) -> Result<Vec<WalletMetadata>, PayError> {
114 dispatch_storage!(self, list_wallet_metadata, network)
115 }
116
117 fn delete_wallet_metadata(&self, wallet_id: &str) -> Result<(), PayError> {
118 dispatch_storage!(self, delete_wallet_metadata, wallet_id)
119 }
120
121 fn wallet_directory_path(&self, wallet_id: &str) -> Result<PathBuf, PayError> {
122 dispatch_storage!(self, wallet_directory_path, wallet_id)
123 }
124
125 fn wallet_data_directory_path(&self, wallet_id: &str) -> Result<PathBuf, PayError> {
126 dispatch_storage!(self, wallet_data_directory_path, wallet_id)
127 }
128
129 fn wallet_data_directory_path_for_meta(&self, meta: &WalletMetadata) -> PathBuf {
130 dispatch_storage!(self, wallet_data_directory_path_for_meta, meta)
131 }
132
133 fn resolve_wallet_id(&self, id_or_label: &str) -> Result<String, PayError> {
134 dispatch_storage!(self, resolve_wallet_id, id_or_label)
135 }
136
137 fn append_transaction_record(&self, record: &HistoryRecord) -> Result<(), PayError> {
138 dispatch_storage!(self, append_transaction_record, record)
139 }
140
141 fn load_wallet_transaction_records(
142 &self,
143 wallet_id: &str,
144 ) -> Result<Vec<HistoryRecord>, PayError> {
145 dispatch_storage!(self, load_wallet_transaction_records, wallet_id)
146 }
147
148 fn find_transaction_record_by_id(
149 &self,
150 tx_id: &str,
151 ) -> Result<Option<HistoryRecord>, PayError> {
152 dispatch_storage!(self, find_transaction_record_by_id, tx_id)
153 }
154
155 fn update_transaction_record_memo(
156 &self,
157 tx_id: &str,
158 memo: Option<&BTreeMap<String, String>>,
159 ) -> Result<(), PayError> {
160 dispatch_storage!(self, update_transaction_record_memo, tx_id, memo)
161 }
162
163 fn update_transaction_record_fee(
164 &self,
165 tx_id: &str,
166 fee_value: u64,
167 fee_unit: &str,
168 ) -> Result<(), PayError> {
169 dispatch_storage!(
170 self,
171 update_transaction_record_fee,
172 tx_id,
173 fee_value,
174 fee_unit
175 )
176 }
177
178 fn update_transaction_record_status(
179 &self,
180 tx_id: &str,
181 status: crate::types::TxStatus,
182 confirmed_at_epoch_s: Option<u64>,
183 ) -> Result<(), PayError> {
184 dispatch_storage!(
185 self,
186 update_transaction_record_status,
187 tx_id,
188 status,
189 confirmed_at_epoch_s
190 )
191 }
192
193 fn drain_migration_log(&self) -> Vec<MigrationLog> {
194 dispatch_storage!(self, drain_migration_log)
195 }
196}
197
198pub fn create_storage_backend(config: &crate::types::RuntimeConfig) -> Option<StorageBackend> {
202 let requested = config.storage_backend.as_deref().unwrap_or("redb");
203
204 match requested {
205 #[cfg(feature = "redb")]
206 "redb" => Some(StorageBackend::Redb(redb_store::RedbStore::new(
207 &config.data_dir,
208 ))),
209 #[cfg(feature = "postgres")]
210 "postgres" => {
211 let url = config.postgres_url_secret.as_deref()?;
212 let data_dir = config.data_dir.clone();
213 tokio::task::block_in_place(|| {
214 tokio::runtime::Handle::current().block_on(async {
215 match postgres_store::PostgresStore::connect(url, &data_dir).await {
216 Ok(store) => Some(StorageBackend::Postgres(store)),
217 Err(_) => None,
218 }
219 })
220 })
221 }
222 _ => None,
223 }
224}
225
226#[cfg(feature = "postgres")]
228#[allow(dead_code)]
229pub async fn create_postgres_backend(
230 config: &crate::types::RuntimeConfig,
231) -> Result<StorageBackend, String> {
232 let url = config.postgres_url_secret.as_deref().ok_or_else(|| {
233 "postgres_url_secret is required when storage_backend = postgres".to_string()
234 })?;
235 let store = postgres_store::PostgresStore::connect(url, &config.data_dir)
236 .await
237 .map_err(|e| format!("postgres connection failed: {e}"))?;
238 Ok(StorageBackend::Postgres(store))
239}