mokshamint/database/
postgres.rs

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) // FIXME make max connections configurable
26                .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}