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
65 fn drain_migration_log(&self) -> Vec<MigrationLog>;
67}
68
69#[derive(Clone)]
71pub enum StorageBackend {
72 #[cfg(feature = "redb")]
73 Redb(redb_store::RedbStore),
74 #[cfg(feature = "postgres")]
75 Postgres(postgres_store::PostgresStore),
76 #[doc(hidden)]
79 _None(std::convert::Infallible),
80}
81
82impl PayStore for StorageBackend {
83 fn save_wallet_metadata(&self, meta: &WalletMetadata) -> Result<(), PayError> {
84 match self {
85 #[cfg(feature = "redb")]
86 Self::Redb(s) => s.save_wallet_metadata(meta),
87 #[cfg(feature = "postgres")]
88 Self::Postgres(s) => s.save_wallet_metadata(meta),
89 Self::_None(n) => match *n {},
90 }
91 }
92
93 fn load_wallet_metadata(&self, wallet_id: &str) -> Result<WalletMetadata, PayError> {
94 match self {
95 #[cfg(feature = "redb")]
96 Self::Redb(s) => s.load_wallet_metadata(wallet_id),
97 #[cfg(feature = "postgres")]
98 Self::Postgres(s) => s.load_wallet_metadata(wallet_id),
99 Self::_None(n) => match *n {},
100 }
101 }
102
103 fn list_wallet_metadata(
104 &self,
105 network: Option<Network>,
106 ) -> Result<Vec<WalletMetadata>, PayError> {
107 match self {
108 #[cfg(feature = "redb")]
109 Self::Redb(s) => s.list_wallet_metadata(network),
110 #[cfg(feature = "postgres")]
111 Self::Postgres(s) => s.list_wallet_metadata(network),
112 Self::_None(n) => match *n {},
113 }
114 }
115
116 fn delete_wallet_metadata(&self, wallet_id: &str) -> Result<(), PayError> {
117 match self {
118 #[cfg(feature = "redb")]
119 Self::Redb(s) => s.delete_wallet_metadata(wallet_id),
120 #[cfg(feature = "postgres")]
121 Self::Postgres(s) => s.delete_wallet_metadata(wallet_id),
122 Self::_None(n) => match *n {},
123 }
124 }
125
126 fn wallet_directory_path(&self, wallet_id: &str) -> Result<PathBuf, PayError> {
127 match self {
128 #[cfg(feature = "redb")]
129 Self::Redb(s) => s.wallet_directory_path(wallet_id),
130 #[cfg(feature = "postgres")]
131 Self::Postgres(s) => s.wallet_directory_path(wallet_id),
132 Self::_None(n) => match *n {},
133 }
134 }
135
136 fn wallet_data_directory_path(&self, wallet_id: &str) -> Result<PathBuf, PayError> {
137 match self {
138 #[cfg(feature = "redb")]
139 Self::Redb(s) => s.wallet_data_directory_path(wallet_id),
140 #[cfg(feature = "postgres")]
141 Self::Postgres(s) => s.wallet_data_directory_path(wallet_id),
142 Self::_None(n) => match *n {},
143 }
144 }
145
146 fn wallet_data_directory_path_for_meta(&self, meta: &WalletMetadata) -> PathBuf {
147 match self {
148 #[cfg(feature = "redb")]
149 Self::Redb(s) => s.wallet_data_directory_path_for_meta(meta),
150 #[cfg(feature = "postgres")]
151 Self::Postgres(s) => s.wallet_data_directory_path_for_meta(meta),
152 Self::_None(n) => match *n {},
153 }
154 }
155
156 fn resolve_wallet_id(&self, id_or_label: &str) -> Result<String, PayError> {
157 match self {
158 #[cfg(feature = "redb")]
159 Self::Redb(s) => s.resolve_wallet_id(id_or_label),
160 #[cfg(feature = "postgres")]
161 Self::Postgres(s) => s.resolve_wallet_id(id_or_label),
162 Self::_None(n) => match *n {},
163 }
164 }
165
166 fn append_transaction_record(&self, record: &HistoryRecord) -> Result<(), PayError> {
167 match self {
168 #[cfg(feature = "redb")]
169 Self::Redb(s) => s.append_transaction_record(record),
170 #[cfg(feature = "postgres")]
171 Self::Postgres(s) => s.append_transaction_record(record),
172 Self::_None(n) => match *n {},
173 }
174 }
175
176 fn load_wallet_transaction_records(
177 &self,
178 wallet_id: &str,
179 ) -> Result<Vec<HistoryRecord>, PayError> {
180 match self {
181 #[cfg(feature = "redb")]
182 Self::Redb(s) => s.load_wallet_transaction_records(wallet_id),
183 #[cfg(feature = "postgres")]
184 Self::Postgres(s) => s.load_wallet_transaction_records(wallet_id),
185 Self::_None(n) => match *n {},
186 }
187 }
188
189 fn find_transaction_record_by_id(
190 &self,
191 tx_id: &str,
192 ) -> Result<Option<HistoryRecord>, PayError> {
193 match self {
194 #[cfg(feature = "redb")]
195 Self::Redb(s) => s.find_transaction_record_by_id(tx_id),
196 #[cfg(feature = "postgres")]
197 Self::Postgres(s) => s.find_transaction_record_by_id(tx_id),
198 Self::_None(n) => match *n {},
199 }
200 }
201
202 fn update_transaction_record_memo(
203 &self,
204 tx_id: &str,
205 memo: Option<&BTreeMap<String, String>>,
206 ) -> Result<(), PayError> {
207 match self {
208 #[cfg(feature = "redb")]
209 Self::Redb(s) => s.update_transaction_record_memo(tx_id, memo),
210 #[cfg(feature = "postgres")]
211 Self::Postgres(s) => s.update_transaction_record_memo(tx_id, memo),
212 Self::_None(n) => match *n {},
213 }
214 }
215
216 fn update_transaction_record_fee(
217 &self,
218 tx_id: &str,
219 fee_value: u64,
220 fee_unit: &str,
221 ) -> Result<(), PayError> {
222 match self {
223 #[cfg(feature = "redb")]
224 Self::Redb(s) => s.update_transaction_record_fee(tx_id, fee_value, fee_unit),
225 #[cfg(feature = "postgres")]
226 Self::Postgres(s) => s.update_transaction_record_fee(tx_id, fee_value, fee_unit),
227 Self::_None(n) => match *n {},
228 }
229 }
230
231 fn drain_migration_log(&self) -> Vec<MigrationLog> {
232 match self {
233 #[cfg(feature = "redb")]
234 Self::Redb(s) => s.drain_migration_log(),
235 #[cfg(feature = "postgres")]
236 Self::Postgres(s) => s.drain_migration_log(),
237 Self::_None(n) => match *n {},
238 }
239 }
240}
241
242pub fn create_storage_backend(config: &crate::types::RuntimeConfig) -> Option<StorageBackend> {
246 let requested = config.storage_backend.as_deref().unwrap_or("redb");
247
248 match requested {
249 #[cfg(feature = "redb")]
250 "redb" => Some(StorageBackend::Redb(redb_store::RedbStore::new(
251 &config.data_dir,
252 ))),
253 #[cfg(feature = "postgres")]
254 "postgres" => {
255 let url = config.postgres_url_secret.as_deref()?;
256 let data_dir = config.data_dir.clone();
257 tokio::task::block_in_place(|| {
258 tokio::runtime::Handle::current().block_on(async {
259 match postgres_store::PostgresStore::connect(url, &data_dir).await {
260 Ok(store) => Some(StorageBackend::Postgres(store)),
261 Err(_) => None,
262 }
263 })
264 })
265 }
266 _ => None,
267 }
268}
269
270#[cfg(feature = "postgres")]
272#[allow(dead_code)]
273pub async fn create_postgres_backend(
274 config: &crate::types::RuntimeConfig,
275) -> Result<StorageBackend, String> {
276 let url = config.postgres_url_secret.as_deref().ok_or_else(|| {
277 "postgres_url_secret is required when storage_backend = postgres".to_string()
278 })?;
279 let store = postgres_store::PostgresStore::connect(url, &config.data_dir)
280 .await
281 .map_err(|e| format!("postgres connection failed: {e}"))?;
282 Ok(StorageBackend::Postgres(store))
283}