1use async_trait::async_trait;
2use moksha_core::{
3 dhke,
4 primitives::{
5 Bolt11MeltQuote, Bolt11MintQuote, CurrencyUnit, OnchainMeltQuote, OnchainMintQuote,
6 },
7 proof::{Proof, Proofs},
8};
9
10use sqlx::postgres::PgPoolOptions;
11use uuid::Uuid;
12
13use crate::{config::DatabaseConfig, error::MokshaMintError, model::Invoice};
14
15use super::Database;
16
17pub struct PostgresDB {
18 pool: sqlx::Pool<sqlx::Postgres>,
19}
20
21impl PostgresDB {
22 pub async fn new(config: &DatabaseConfig) -> Result<Self, sqlx::Error> {
23 Ok(Self {
24 pool: PgPoolOptions::new()
25 .max_connections(5) .connect(config.db_url.as_str())
27 .await?,
28 })
29 }
30
31 pub async fn migrate(&self) {
32 sqlx::migrate!("./migrations")
33 .run(&self.pool)
34 .await
35 .expect("Could not run migrations");
36 }
37
38 pub async fn start_transaction(
39 &self,
40 ) -> Result<sqlx::Transaction<'_, sqlx::Postgres>, sqlx::Error> {
41 self.pool.begin().await
42 }
43
44 pub async fn commit_transaction(
45 &self,
46 transaction: sqlx::Transaction<'_, sqlx::Postgres>,
47 ) -> Result<(), sqlx::Error> {
48 transaction.commit().await
49 }
50}
51
52#[async_trait]
53impl Database for PostgresDB {
54 async fn get_used_proofs(&self) -> Result<Proofs, MokshaMintError> {
55 let proofs = sqlx::query!("SELECT * FROM used_proofs")
56 .fetch_all(&self.pool)
57 .await?
58 .into_iter()
59 .map(|row| Proof {
60 amount: row.amount as u64,
61 secret: row.secret,
62 c: dhke::public_key_from_hex(&row.c).to_owned(),
63 keyset_id: row.keyset_id,
64 script: None,
65 })
66 .collect::<Vec<Proof>>();
67
68 Ok(proofs.into())
69 }
70
71 async fn add_used_proofs(&self, proofs: &Proofs) -> Result<(), MokshaMintError> {
72 for proof in proofs.proofs() {
73 sqlx::query!(
74 "INSERT INTO used_proofs (amount, secret, c, keyset_id) VALUES ($1, $2, $3, $4)",
75 proof.amount as i64,
76 proof.secret,
77 proof.c.to_string(),
78 proof.keyset_id.to_string()
79 )
80 .execute(&self.pool)
81 .await?;
82 }
83
84 Ok(())
85 }
86
87 async fn get_pending_invoice(&self, key: String) -> Result<Invoice, MokshaMintError> {
88 let invoice: Invoice = sqlx::query!(
89 "SELECT amount, payment_request FROM pending_invoices WHERE key = $1",
90 key
91 )
92 .map(|row| Invoice {
93 amount: row.amount as u64,
94 payment_request: row.payment_request,
95 })
96 .fetch_one(&self.pool)
97 .await?;
98
99 Ok(invoice)
100 }
101
102 async fn add_pending_invoice(
103 &self,
104 key: String,
105 invoice: &Invoice,
106 ) -> Result<(), MokshaMintError> {
107 sqlx::query!(
108 "INSERT INTO pending_invoices (key, amount, payment_request) VALUES ($1, $2, $3)",
109 key,
110 invoice.amount as i64,
111 invoice.payment_request
112 )
113 .execute(&self.pool)
114 .await?;
115
116 Ok(())
117 }
118
119 async fn delete_pending_invoice(&self, key: String) -> Result<(), MokshaMintError> {
120 sqlx::query!("DELETE FROM pending_invoices WHERE key = $1", key)
121 .execute(&self.pool)
122 .await?;
123 Ok(())
124 }
125
126 async fn get_bolt11_mint_quote(&self, id: &Uuid) -> Result<Bolt11MintQuote, MokshaMintError> {
127 let quote: Bolt11MintQuote = sqlx::query!(
128 "SELECT id, payment_request, expiry, paid FROM bolt11_mint_quotes WHERE id = $1",
129 id
130 )
131 .map(|row| Bolt11MintQuote {
132 quote_id: row.id,
133 payment_request: row.payment_request,
134 expiry: row.expiry as u64,
135 paid: row.paid,
136 })
137 .fetch_one(&self.pool)
138 .await?;
139 Ok(quote)
140 }
141
142 async fn add_bolt11_mint_quote(&self, quote: &Bolt11MintQuote) -> Result<(), MokshaMintError> {
143 sqlx::query!(
144 "INSERT INTO bolt11_mint_quotes (id, payment_request, expiry, paid) VALUES ($1, $2, $3, $4)",
145 quote.quote_id,
146 quote.payment_request,
147 quote.expiry as i64,
148 quote.paid
149 )
150 .execute(&self.pool)
151 .await?;
152 Ok(())
153 }
154
155 async fn update_bolt11_mint_quote(
156 &self,
157 quote: &Bolt11MintQuote,
158 ) -> Result<(), MokshaMintError> {
159 sqlx::query!(
160 "UPDATE bolt11_mint_quotes SET paid = $1 WHERE id = $2",
161 quote.paid,
162 quote.quote_id
163 )
164 .execute(&self.pool)
165 .await?;
166 Ok(())
167 }
168
169 async fn delete_bolt11_mint_quote(
170 &self,
171 quote: &Bolt11MintQuote,
172 ) -> Result<(), MokshaMintError> {
173 sqlx::query!(
174 "DELETE FROM bolt11_mint_quotes WHERE id = $1",
175 quote.quote_id
176 )
177 .execute(&self.pool)
178 .await?;
179 Ok(())
180 }
181
182 async fn get_bolt11_melt_quote(&self, key: &Uuid) -> Result<Bolt11MeltQuote, MokshaMintError> {
183 let quote: Bolt11MeltQuote = sqlx::query!(
184 "SELECT id, payment_request, expiry, paid, amount, fee_reserve FROM bolt11_melt_quotes WHERE id = $1",
185 key
186 )
187 .map(|row| Bolt11MeltQuote {
188 quote_id: row.id,
189 payment_request: row.payment_request,
190 expiry: row.expiry as u64,
191 paid: row.paid,
192 amount: row.amount as u64,
193 fee_reserve: row.fee_reserve as u64,
194 })
195 .fetch_one(&self.pool)
196 .await?;
197
198 Ok(quote)
199 }
200
201 async fn add_bolt11_melt_quote(&self, quote: &Bolt11MeltQuote) -> Result<(), MokshaMintError> {
202 sqlx::query!(
203 "INSERT INTO bolt11_melt_quotes (id, payment_request, expiry, paid, amount, fee_reserve) VALUES ($1, $2, $3, $4, $5, $6)",
204 quote.quote_id,
205 quote.payment_request,
206 quote.expiry as i64,
207 quote.paid,
208 quote.amount as i64,
209 quote.fee_reserve as i64
210 )
211 .execute(&self.pool)
212 .await?;
213 Ok(())
214 }
215
216 async fn update_bolt11_melt_quote(
217 &self,
218 quote: &Bolt11MeltQuote,
219 ) -> Result<(), MokshaMintError> {
220 sqlx::query!(
221 "UPDATE bolt11_melt_quotes SET paid = $1 WHERE id = $2",
222 quote.paid,
223 quote.quote_id
224 )
225 .execute(&self.pool)
226 .await?;
227 Ok(())
228 }
229
230 async fn delete_bolt11_melt_quote(
231 &self,
232 quote: &Bolt11MeltQuote,
233 ) -> Result<(), MokshaMintError> {
234 sqlx::query!(
235 "DELETE FROM bolt11_melt_quotes WHERE id = $1",
236 quote.quote_id
237 )
238 .execute(&self.pool)
239 .await?;
240 Ok(())
241 }
242
243 async fn get_onchain_mint_quote(
244 &self,
245 key: &Uuid,
246 ) -> Result<OnchainMintQuote, MokshaMintError> {
247 let quote: OnchainMintQuote = sqlx::query!(
248 "SELECT id, address, amount, expiry, paid FROM onchain_mint_quotes WHERE id = $1",
249 key
250 )
251 .map(|row| OnchainMintQuote {
252 quote_id: row.id,
253 address: row.address,
254 expiry: row.expiry as u64,
255 paid: row.paid,
256 amount: row.amount as u64,
257 unit: CurrencyUnit::Sat,
258 })
259 .fetch_one(&self.pool)
260 .await?;
261
262 Ok(quote)
263 }
264 async fn add_onchain_mint_quote(
265 &self,
266 quote: &OnchainMintQuote,
267 ) -> Result<(), MokshaMintError> {
268 sqlx::query!(
269 "INSERT INTO onchain_mint_quotes (id, address, amount, expiry, paid) VALUES ($1, $2, $3, $4, $5)",
270 quote.quote_id,
271 quote.address,
272 quote.amount as i64,
273 quote.expiry as i64,
274 quote.paid,
275 )
276 .execute(&self.pool)
277 .await?;
278 Ok(())
279 }
280
281 async fn update_onchain_mint_quote(
282 &self,
283 quote: &OnchainMintQuote,
284 ) -> Result<(), MokshaMintError> {
285 sqlx::query!(
286 "UPDATE onchain_mint_quotes SET paid = $1 WHERE id = $2",
287 quote.paid,
288 quote.quote_id
289 )
290 .execute(&self.pool)
291 .await?;
292 Ok(())
293 }
294
295 async fn delete_onchain_mint_quote(
296 &self,
297 quote: &OnchainMintQuote,
298 ) -> Result<(), MokshaMintError> {
299 sqlx::query!(
300 "DELETE FROM onchain_mint_quotes WHERE id = $1",
301 quote.quote_id
302 )
303 .execute(&self.pool)
304 .await?;
305 Ok(())
306 }
307
308 async fn get_onchain_melt_quote(
309 &self,
310 key: &Uuid,
311 ) -> Result<OnchainMeltQuote, MokshaMintError> {
312 let quote: OnchainMeltQuote = sqlx::query!(
313 "SELECT id, amount,address, fee_total, fee_sat_per_vbyte, expiry, paid FROM onchain_melt_quotes WHERE id = $1",
314 key
315 )
316 .map(|row| OnchainMeltQuote {
317 quote_id: row.id,
318 address: row.address,
319 amount: row.amount as u64,
320 fee_total: row.fee_total as u64,
321 fee_sat_per_vbyte: row.fee_sat_per_vbyte as u32,
322 expiry: row.expiry as u64,
323 paid: row.paid,
324 })
325 .fetch_one(&self.pool)
326 .await?;
327
328 Ok(quote)
329 }
330 async fn add_onchain_melt_quote(
331 &self,
332 quote: &OnchainMeltQuote,
333 ) -> Result<(), MokshaMintError> {
334 sqlx::query!(
335 "INSERT INTO onchain_melt_quotes (id, amount, address, fee_total, fee_sat_per_vbyte, expiry, paid) VALUES ($1, $2, $3, $4, $5, $6, $7)",
336 quote.quote_id,
337 quote.amount as i64,
338 quote.address,
339 quote.fee_total as i64,
340 quote.fee_sat_per_vbyte as i64,
341 quote.expiry as i64,
342 quote.paid,
343 )
344 .execute(&self.pool)
345 .await?;
346 Ok(())
347 }
348 async fn update_onchain_melt_quote(
349 &self,
350 quote: &OnchainMeltQuote,
351 ) -> Result<(), MokshaMintError> {
352 sqlx::query!(
353 "UPDATE onchain_melt_quotes SET paid = $1 WHERE id = $2",
354 quote.paid,
355 quote.quote_id
356 )
357 .execute(&self.pool)
358 .await?;
359 Ok(())
360 }
361 async fn delete_onchain_melt_quote(
362 &self,
363 quote: &OnchainMeltQuote,
364 ) -> Result<(), MokshaMintError> {
365 sqlx::query!(
366 "DELETE FROM onchain_melt_quotes WHERE id = $1",
367 quote.quote_id
368 )
369 .execute(&self.pool)
370 .await?;
371 Ok(())
372 }
373}