1use crate::util::get_mcli_path;
2use anyhow::Result;
3use mostro_core::order::SmallOrder;
4use mostro_core::NOSTR_REPLACEABLE_EVENT_KIND;
5use nip06::FromMnemonic;
6use nostr_sdk::prelude::*;
7use sqlx::pool::Pool;
8use sqlx::Sqlite;
9use sqlx::SqlitePool;
10use std::fs::File;
11use std::path::Path;
12
13pub async fn connect() -> Result<Pool<Sqlite>> {
14 let mcli_dir = get_mcli_path();
15 let mcli_db_path = format!("{}/mcli.db", mcli_dir);
16 let db_url = format!("sqlite://{}", mcli_db_path);
17 let pool: Pool<Sqlite>;
18 if !Path::exists(Path::new(&mcli_db_path)) {
19 if let Err(res) = File::create(&mcli_db_path) {
20 println!("Error in creating db file: {}", res);
21 return Err(res.into());
22 }
23 pool = SqlitePool::connect(&db_url).await?;
24 println!("Creating database file with orders table...");
25 sqlx::query(
26 r#"
27 CREATE TABLE IF NOT EXISTS orders (
28 id TEXT PRIMARY KEY,
29 kind TEXT NOT NULL,
30 status TEXT NOT NULL,
31 amount INTEGER NOT NULL,
32 min_amount INTEGER,
33 max_amount INTEGER,
34 fiat_code TEXT NOT NULL,
35 fiat_amount INTEGER NOT NULL,
36 payment_method TEXT NOT NULL,
37 premium INTEGER NOT NULL,
38 trade_keys TEXT,
39 counterparty_pubkey TEXT,
40 is_mine BOOLEAN,
41 buyer_invoice TEXT,
42 buyer_token INTEGER,
43 seller_token INTEGER,
44 request_id INTEGER,
45 created_at INTEGER,
46 expires_at INTEGER
47 );
48 CREATE TABLE IF NOT EXISTS users (
49 i0_pubkey char(64) PRIMARY KEY,
50 mnemonic TEXT,
51 last_trade_index INTEGER,
52 created_at INTEGER
53 );
54 "#,
55 )
56 .execute(&pool)
57 .await?;
58
59 let mnemonic = match Mnemonic::generate(12) {
60 Ok(m) => m.to_string(),
61 Err(e) => {
62 println!("Error generating mnemonic: {}", e);
63 return Err(e.into());
64 }
65 };
66 let user = User::new(mnemonic, &pool).await?;
67 println!("User created with pubkey: {}", user.i0_pubkey);
68 } else {
69 pool = SqlitePool::connect(&db_url).await?;
70 }
71
72 Ok(pool)
73}
74
75#[derive(Debug, Default, Clone, sqlx::FromRow)]
76pub struct User {
77 pub i0_pubkey: String,
79 pub mnemonic: String,
80 pub last_trade_index: Option<i64>,
81 pub created_at: i64,
82}
83
84impl User {
85 pub async fn new(mnemonic: String, pool: &SqlitePool) -> Result<Self> {
86 let mut user = User::default();
87 let account = NOSTR_REPLACEABLE_EVENT_KIND as u32;
88 let i0_keys =
89 Keys::from_mnemonic_advanced(&mnemonic, None, Some(account), Some(0), Some(0))?;
90 user.i0_pubkey = i0_keys.public_key().to_string();
91 user.created_at = chrono::Utc::now().timestamp();
92 user.mnemonic = mnemonic;
93 sqlx::query(
94 r#"
95 INSERT INTO users (i0_pubkey, mnemonic, created_at)
96 VALUES (?, ?, ?)
97 "#,
98 )
99 .bind(&user.i0_pubkey)
100 .bind(&user.mnemonic)
101 .bind(user.created_at)
102 .execute(pool)
103 .await?;
104
105 Ok(user)
106 }
107 pub fn set_mnemonic(&mut self, mnemonic: String) -> &mut Self {
109 self.mnemonic = mnemonic;
110 self
111 }
112
113 pub fn set_last_trade_index(&mut self, last_trade_index: i64) -> &mut Self {
114 self.last_trade_index = Some(last_trade_index);
115 self
116 }
117
118 pub async fn save(&self, pool: &SqlitePool) -> Result<()> {
120 sqlx::query(
121 r#"
122 UPDATE users
123 SET mnemonic = ?, last_trade_index = ?
124 WHERE i0_pubkey = ?
125 "#,
126 )
127 .bind(&self.mnemonic)
128 .bind(self.last_trade_index)
129 .bind(&self.i0_pubkey)
130 .execute(pool)
131 .await?;
132
133 println!(
134 "User with i0 pubkey {} updated in the database.",
135 self.i0_pubkey
136 );
137
138 Ok(())
139 }
140
141 pub async fn get(pool: &SqlitePool) -> Result<User> {
142 let user = sqlx::query_as::<_, User>(
143 r#"
144 SELECT i0_pubkey, mnemonic, last_trade_index, created_at
145 FROM users
146 LIMIT 1
147 "#,
148 )
149 .fetch_one(pool)
150 .await?;
151
152 Ok(user)
153 }
154
155 pub async fn get_next_trade_index(pool: SqlitePool) -> Result<i64> {
156 let user = User::get(&pool).await?;
157 match user.last_trade_index {
158 Some(index) => Ok(index + 1),
159 None => Ok(1),
160 }
161 }
162
163 pub async fn get_identity_keys(pool: &SqlitePool) -> Result<Keys> {
164 let user = User::get(pool).await?;
165 let account = NOSTR_REPLACEABLE_EVENT_KIND as u32;
166 let keys =
167 Keys::from_mnemonic_advanced(&user.mnemonic, None, Some(account), Some(0), Some(0))?;
168
169 Ok(keys)
170 }
171
172 pub async fn get_next_trade_keys(pool: &SqlitePool) -> Result<(Keys, i64)> {
173 let trade_index = User::get_next_trade_index(pool.clone()).await?;
174
175 let user = User::get(pool).await?;
176 let account = NOSTR_REPLACEABLE_EVENT_KIND as u32;
177 match trade_index.try_into() {
178 Ok(index) => {
179 let keys = Keys::from_mnemonic_advanced(
180 &user.mnemonic,
181 None,
182 Some(account),
183 Some(0),
184 Some(index),
185 )?;
186 Ok((keys, trade_index))
187 }
188 Err(e) => {
189 println!("Error: {}", e);
190 Err(e.into())
191 }
192 }
193 }
194
195 pub async fn get_trade_keys(pool: &SqlitePool, index: i64) -> Result<Keys> {
196 if index < 0 {
197 return Err(anyhow::anyhow!("Trade index cannot be negative"));
198 }
199 let user = User::get(pool).await?;
200 let account = NOSTR_REPLACEABLE_EVENT_KIND as u32;
201 let keys = Keys::from_mnemonic_advanced(
202 &user.mnemonic,
203 None,
204 Some(account),
205 Some(0),
206 Some(index as u32),
207 )?;
208 Ok(keys)
209 }
210}
211
212#[derive(Debug, Default, Clone, sqlx::FromRow)]
213pub struct Order {
214 pub id: Option<String>,
215 pub kind: Option<String>,
216 pub status: Option<String>,
217 pub amount: i64,
218 pub fiat_code: String,
219 pub min_amount: Option<i64>,
220 pub max_amount: Option<i64>,
221 pub fiat_amount: i64,
222 pub payment_method: String,
223 pub premium: i64,
224 pub trade_keys: Option<String>,
225 pub counterparty_pubkey: Option<String>,
226 pub is_mine: Option<bool>,
227 pub buyer_invoice: Option<String>,
228 pub buyer_token: Option<u16>,
229 pub seller_token: Option<u16>,
230 pub request_id: Option<i64>,
231 pub created_at: Option<i64>,
232 pub expires_at: Option<i64>,
233}
234
235impl Order {
236 pub async fn new(
237 pool: &SqlitePool,
238 order: SmallOrder,
239 trade_keys: &Keys,
240 request_id: Option<i64>,
241 ) -> Result<Self> {
242 let trade_keys_hex = trade_keys.secret_key().to_secret_hex();
243 let id = match order.id {
244 Some(id) => id.to_string(),
245 None => uuid::Uuid::new_v4().to_string(),
246 };
247 let order = Order {
248 id: Some(id),
249 kind: order.kind.as_ref().map(|k| k.to_string()),
250 status: order.status.as_ref().map(|s| s.to_string()),
251 amount: order.amount,
252 fiat_code: order.fiat_code,
253 min_amount: order.min_amount,
254 max_amount: order.max_amount,
255 fiat_amount: order.fiat_amount,
256 payment_method: order.payment_method,
257 premium: order.premium,
258 trade_keys: Some(trade_keys_hex),
259 counterparty_pubkey: None,
260 is_mine: Some(true),
261 buyer_invoice: None,
262 buyer_token: None,
263 seller_token: None,
264 request_id,
265 created_at: Some(chrono::Utc::now().timestamp()),
266 expires_at: None,
267 };
268
269 sqlx::query(
270 r#"
271 INSERT INTO orders (id, kind, status, amount, min_amount, max_amount,
272 fiat_code, fiat_amount, payment_method, premium, trade_keys,
273 counterparty_pubkey, is_mine, buyer_invoice, buyer_token, seller_token,
274 request_id, created_at, expires_at)
275 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
276 "#,
277 )
278 .bind(&order.id)
279 .bind(&order.kind)
280 .bind(&order.status)
281 .bind(order.amount)
282 .bind(order.min_amount)
283 .bind(order.max_amount)
284 .bind(&order.fiat_code)
285 .bind(order.fiat_amount)
286 .bind(&order.payment_method)
287 .bind(order.premium)
288 .bind(&order.trade_keys)
289 .bind(&order.counterparty_pubkey)
290 .bind(order.is_mine)
291 .bind(&order.buyer_invoice)
292 .bind(order.buyer_token)
293 .bind(order.seller_token)
294 .bind(order.request_id)
295 .bind(order.created_at)
296 .bind(order.expires_at)
297 .execute(pool)
298 .await?;
299
300 Ok(order)
301 }
302
303 pub fn set_kind(&mut self, kind: String) -> &mut Self {
305 self.kind = Some(kind);
306 self
307 }
308
309 pub fn set_status(&mut self, status: String) -> &mut Self {
310 self.status = Some(status);
311 self
312 }
313
314 pub fn set_amount(&mut self, amount: i64) -> &mut Self {
315 self.amount = amount;
316 self
317 }
318
319 pub fn set_fiat_code(&mut self, fiat_code: String) -> &mut Self {
320 self.fiat_code = fiat_code;
321 self
322 }
323
324 pub fn set_min_amount(&mut self, min_amount: i64) -> &mut Self {
325 self.min_amount = Some(min_amount);
326 self
327 }
328
329 pub fn set_max_amount(&mut self, max_amount: i64) -> &mut Self {
330 self.max_amount = Some(max_amount);
331 self
332 }
333
334 pub fn set_fiat_amount(&mut self, fiat_amount: i64) -> &mut Self {
335 self.fiat_amount = fiat_amount;
336 self
337 }
338
339 pub fn set_payment_method(&mut self, payment_method: String) -> &mut Self {
340 self.payment_method = payment_method;
341 self
342 }
343
344 pub fn set_premium(&mut self, premium: i64) -> &mut Self {
345 self.premium = premium;
346 self
347 }
348
349 pub fn set_counterparty_pubkey(&mut self, counterparty_pubkey: String) -> &mut Self {
350 self.counterparty_pubkey = Some(counterparty_pubkey);
351 self
352 }
353
354 pub fn set_trade_keys(&mut self, trade_keys: String) -> &mut Self {
355 self.trade_keys = Some(trade_keys);
356 self
357 }
358
359 pub fn set_is_mine(&mut self, is_mine: bool) -> &mut Self {
360 self.is_mine = Some(is_mine);
361 self
362 }
363
364 pub async fn save(&self, pool: &SqlitePool) -> Result<()> {
366 if let Some(ref id) = self.id {
368 sqlx::query(
369 r#"
370 UPDATE orders
371 SET kind = ?, status = ?, amount = ?, fiat_code = ?, min_amount = ?, max_amount = ?,
372 fiat_amount = ?, payment_method = ?, premium = ?, trade_keys = ?, counterparty_pubkey = ?,
373 is_mine = ?, buyer_invoice = ?, created_at = ?, expires_at = ?, buyer_token = ?,
374 seller_token = ?
375 WHERE id = ?
376 "#,
377 )
378 .bind(&self.kind)
379 .bind(&self.status)
380 .bind(self.amount)
381 .bind(&self.fiat_code)
382 .bind(self.min_amount)
383 .bind(self.max_amount)
384 .bind(self.fiat_amount)
385 .bind(&self.payment_method)
386 .bind(self.premium)
387 .bind(&self.trade_keys)
388 .bind(&self.counterparty_pubkey)
389 .bind(self.is_mine)
390 .bind(&self.buyer_invoice)
391 .bind(self.created_at)
392 .bind(self.expires_at)
393 .bind(self.buyer_token)
394 .bind(self.seller_token)
395 .bind(id)
396 .execute(pool)
397 .await?;
398
399 println!("Order with id {} updated in the database.", id);
400 } else {
401 return Err(anyhow::anyhow!("Order must have an ID to be updated."));
402 }
403
404 Ok(())
405 }
406
407 pub async fn save_new_id(
408 pool: &SqlitePool,
409 id: String,
410 new_id: String,
411 ) -> anyhow::Result<bool> {
412 let rows_affected = sqlx::query(
413 r#"
414 UPDATE orders
415 SET id = ?
416 WHERE id = ?
417 "#,
418 )
419 .bind(&new_id)
420 .bind(&id)
421 .execute(pool)
422 .await?
423 .rows_affected();
424
425 Ok(rows_affected > 0)
426 }
427
428 pub async fn get_by_id(pool: &SqlitePool, id: &str) -> Result<Order> {
429 let order = sqlx::query_as::<_, Order>(
430 r#"
431 SELECT * FROM orders WHERE id = ?
432 LIMIT 1
433 "#,
434 )
435 .bind(id)
436 .fetch_one(pool)
437 .await?;
438
439 if order.id.is_none() {
440 return Err(anyhow::anyhow!("Order not found"));
441 }
442
443 Ok(order)
444 }
445
446 pub async fn get_all(pool: &SqlitePool) -> Result<Vec<Order>> {
447 let orders = sqlx::query_as::<_, Order>(r#"SELECT * FROM orders"#)
448 .fetch_all(pool)
449 .await?;
450 Ok(orders)
451 }
452}