1use crate::util::get_mcli_path;
2use anyhow::Result;
3use mostro_core::prelude::*;
4use nip06::FromMnemonic;
5use nostr_sdk::prelude::*;
6use sqlx::pool::Pool;
7use sqlx::Sqlite;
8use sqlx::SqlitePool;
9use std::fs::File;
10use std::path::Path;
11
12pub async fn connect() -> Result<Pool<Sqlite>> {
13 let mcli_dir = get_mcli_path();
14 let mcli_db_path = format!("{}/mcli.db", mcli_dir);
15 let db_url = format!("sqlite://{}", mcli_db_path);
16 let pool: Pool<Sqlite>;
17 if !Path::exists(Path::new(&mcli_db_path)) {
18 if let Err(res) = File::create(&mcli_db_path) {
19 println!("Error in creating db file: {}", res);
20 return Err(res.into());
21 }
22 pool = SqlitePool::connect(&db_url).await?;
23 println!("Creating database file with orders table...");
24 sqlx::query(
25 r#"
26 CREATE TABLE IF NOT EXISTS orders (
27 id TEXT PRIMARY KEY,
28 kind TEXT NOT NULL,
29 status TEXT NOT NULL,
30 amount INTEGER NOT NULL,
31 min_amount INTEGER,
32 max_amount INTEGER,
33 fiat_code TEXT NOT NULL,
34 fiat_amount INTEGER NOT NULL,
35 payment_method TEXT NOT NULL,
36 premium INTEGER NOT NULL,
37 trade_keys TEXT,
38 counterparty_pubkey TEXT,
39 is_mine BOOLEAN,
40 buyer_invoice TEXT,
41 request_id INTEGER,
42 created_at INTEGER,
43 expires_at INTEGER
44 );
45 CREATE TABLE IF NOT EXISTS users (
46 i0_pubkey char(64) PRIMARY KEY,
47 mnemonic TEXT,
48 last_trade_index INTEGER,
49 created_at INTEGER
50 );
51 "#,
52 )
53 .execute(&pool)
54 .await?;
55
56 let mnemonic = match Mnemonic::generate(12) {
57 Ok(m) => m.to_string(),
58 Err(e) => {
59 println!("Error generating mnemonic: {}", e);
60 return Err(e.into());
61 }
62 };
63 let user = User::new(mnemonic, &pool).await?;
64 println!("User created with pubkey: {}", user.i0_pubkey);
65 } else {
66 pool = SqlitePool::connect(&db_url).await?;
67
68 migrate_remove_token_columns(&pool).await?;
70 }
71
72 Ok(pool)
73}
74
75async fn migrate_remove_token_columns(pool: &SqlitePool) -> Result<()> {
76 println!("Checking for legacy token columns...");
77
78 let buyer_token_exists = sqlx::query_scalar::<_, i64>(
80 "SELECT COUNT(*) FROM pragma_table_info('orders') WHERE name = 'buyer_token'",
81 )
82 .fetch_one(pool)
83 .await?;
84
85 let seller_token_exists = sqlx::query_scalar::<_, i64>(
87 "SELECT COUNT(*) FROM pragma_table_info('orders') WHERE name = 'seller_token'",
88 )
89 .fetch_one(pool)
90 .await?;
91
92 if buyer_token_exists > 0 {
94 println!("Removing legacy buyer_token column...");
95 match sqlx::query("ALTER TABLE orders DROP COLUMN buyer_token")
96 .execute(pool)
97 .await
98 {
99 Ok(_) => println!("Successfully removed buyer_token column"),
100 Err(e) => {
101 println!("Warning: Could not remove buyer_token column: {}", e);
102 }
104 }
105 }
106
107 if seller_token_exists > 0 {
109 println!("Removing legacy seller_token column...");
110 match sqlx::query("ALTER TABLE orders DROP COLUMN seller_token")
111 .execute(pool)
112 .await
113 {
114 Ok(_) => println!("Successfully removed seller_token column"),
115 Err(e) => {
116 println!("Warning: Could not remove seller_token column: {}", e);
117 }
119 }
120 }
121
122 if buyer_token_exists == 0 && seller_token_exists == 0 {
123 println!("No legacy token columns found - database is up to date");
124 }
125
126 Ok(())
127}
128
129#[derive(Debug, Default, Clone, sqlx::FromRow)]
130pub struct User {
131 pub i0_pubkey: String,
133 pub mnemonic: String,
134 pub last_trade_index: Option<i64>,
135 pub created_at: i64,
136}
137
138impl User {
139 pub async fn new(mnemonic: String, pool: &SqlitePool) -> Result<Self> {
140 let mut user = User::default();
141 let account = NOSTR_REPLACEABLE_EVENT_KIND as u32;
142 let i0_keys =
143 Keys::from_mnemonic_advanced(&mnemonic, None, Some(account), Some(0), Some(0))?;
144 user.i0_pubkey = i0_keys.public_key().to_string();
145 user.created_at = chrono::Utc::now().timestamp();
146 user.mnemonic = mnemonic;
147 sqlx::query(
148 r#"
149 INSERT INTO users (i0_pubkey, mnemonic, created_at)
150 VALUES (?, ?, ?)
151 "#,
152 )
153 .bind(&user.i0_pubkey)
154 .bind(&user.mnemonic)
155 .bind(user.created_at)
156 .execute(pool)
157 .await?;
158
159 Ok(user)
160 }
161 pub fn set_mnemonic(&mut self, mnemonic: String) -> &mut Self {
163 self.mnemonic = mnemonic;
164 self
165 }
166
167 pub fn set_last_trade_index(&mut self, last_trade_index: i64) -> &mut Self {
168 self.last_trade_index = Some(last_trade_index);
169 self
170 }
171
172 pub async fn save(&self, pool: &SqlitePool) -> Result<()> {
174 sqlx::query(
175 r#"
176 UPDATE users
177 SET mnemonic = ?, last_trade_index = ?
178 WHERE i0_pubkey = ?
179 "#,
180 )
181 .bind(&self.mnemonic)
182 .bind(self.last_trade_index)
183 .bind(&self.i0_pubkey)
184 .execute(pool)
185 .await?;
186
187 println!(
188 "User with i0 pubkey {} updated in the database.",
189 self.i0_pubkey
190 );
191
192 Ok(())
193 }
194
195 pub async fn get(pool: &SqlitePool) -> Result<User> {
196 let user = sqlx::query_as::<_, User>(
197 r#"
198 SELECT i0_pubkey, mnemonic, last_trade_index, created_at
199 FROM users
200 LIMIT 1
201 "#,
202 )
203 .fetch_one(pool)
204 .await?;
205
206 Ok(user)
207 }
208
209 pub async fn get_last_trade_index(pool: SqlitePool) -> Result<i64> {
210 let user = User::get(&pool).await?;
211 match user.last_trade_index {
212 Some(index) => Ok(index),
213 None => Ok(0),
214 }
215 }
216
217 pub async fn get_next_trade_index(pool: SqlitePool) -> Result<i64> {
218 let last_trade_index = User::get_last_trade_index(pool).await?;
219 Ok(last_trade_index + 1)
220 }
221
222 pub async fn get_identity_keys(pool: &SqlitePool) -> Result<Keys> {
223 let user = User::get(pool).await?;
224 let account = NOSTR_REPLACEABLE_EVENT_KIND as u32;
225 let keys =
226 Keys::from_mnemonic_advanced(&user.mnemonic, None, Some(account), Some(0), Some(0))?;
227
228 Ok(keys)
229 }
230
231 pub async fn get_next_trade_keys(pool: &SqlitePool) -> Result<(Keys, i64)> {
232 let trade_index = User::get_next_trade_index(pool.clone()).await?;
233 let trade_keys = User::get_trade_keys(pool, trade_index).await?;
234
235 Ok((trade_keys, trade_index))
236 }
237
238 pub async fn get_trade_keys(pool: &SqlitePool, index: i64) -> Result<Keys> {
239 if index < 0 {
240 return Err(anyhow::anyhow!("Trade index cannot be negative"));
241 }
242 let user = User::get(pool).await?;
243 let account = NOSTR_REPLACEABLE_EVENT_KIND as u32;
244 let keys = Keys::from_mnemonic_advanced(
245 &user.mnemonic,
246 None,
247 Some(account),
248 Some(0),
249 Some(index as u32),
250 )?;
251
252 Ok(keys)
253 }
254}
255
256#[derive(Debug, Default, Clone, sqlx::FromRow)]
257pub struct Order {
258 pub id: Option<String>,
259 pub kind: Option<String>,
260 pub status: Option<String>,
261 pub amount: i64,
262 pub fiat_code: String,
263 pub min_amount: Option<i64>,
264 pub max_amount: Option<i64>,
265 pub fiat_amount: i64,
266 pub payment_method: String,
267 pub premium: i64,
268 pub trade_keys: Option<String>,
269 pub counterparty_pubkey: Option<String>,
270 pub is_mine: Option<bool>,
271 pub buyer_invoice: Option<String>,
272 pub request_id: Option<i64>,
273 pub created_at: Option<i64>,
274 pub expires_at: Option<i64>,
275}
276
277impl Order {
278 pub async fn new(
279 pool: &SqlitePool,
280 order: SmallOrder,
281 trade_keys: &Keys,
282 request_id: Option<i64>,
283 ) -> Result<Self> {
284 let trade_keys_hex = trade_keys.secret_key().to_secret_hex();
285 let id = match order.id {
286 Some(id) => id.to_string(),
287 None => uuid::Uuid::new_v4().to_string(),
288 };
289 let order = Order {
290 id: Some(id),
291 kind: order.kind.as_ref().map(|k| k.to_string()),
292 status: order.status.as_ref().map(|s| s.to_string()),
293 amount: order.amount,
294 fiat_code: order.fiat_code,
295 min_amount: order.min_amount,
296 max_amount: order.max_amount,
297 fiat_amount: order.fiat_amount,
298 payment_method: order.payment_method,
299 premium: order.premium,
300 trade_keys: Some(trade_keys_hex),
301 counterparty_pubkey: None,
302 is_mine: Some(true),
303 buyer_invoice: None,
304 request_id,
305 created_at: Some(chrono::Utc::now().timestamp()),
306 expires_at: None,
307 };
308
309 let insert_result = order.insert_db(pool).await;
311
312 if let Err(e) = insert_result {
313 let is_unique_violation = match e.as_database_error() {
316 Some(db_err) => {
317 let code = db_err.code().map(|c| c.to_string()).unwrap_or_default();
318 code == "1555" || code == "2067"
319 }
320 None => false,
321 };
322
323 if is_unique_violation {
324 order.update_db(pool).await?;
325 } else {
326 return Err(e.into());
327 }
328 }
329
330 Ok(order)
331 }
332
333 async fn insert_db(&self, pool: &SqlitePool) -> Result<(), sqlx::Error> {
334 sqlx::query(
335 r#"
336 INSERT INTO orders (id, kind, status, amount, min_amount, max_amount,
337 fiat_code, fiat_amount, payment_method, premium, trade_keys,
338 counterparty_pubkey, is_mine, buyer_invoice, request_id, created_at, expires_at)
339 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
340 "#,
341 )
342 .bind(&self.id)
343 .bind(&self.kind)
344 .bind(&self.status)
345 .bind(self.amount)
346 .bind(self.min_amount)
347 .bind(self.max_amount)
348 .bind(&self.fiat_code)
349 .bind(self.fiat_amount)
350 .bind(&self.payment_method)
351 .bind(self.premium)
352 .bind(&self.trade_keys)
353 .bind(&self.counterparty_pubkey)
354 .bind(self.is_mine)
355 .bind(&self.buyer_invoice)
356 .bind(self.request_id)
357 .bind(self.created_at)
358 .bind(self.expires_at)
359 .execute(pool)
360 .await?
361 .rows_affected();
362 Ok(())
363 }
364
365 async fn update_db(&self, pool: &SqlitePool) -> Result<(), sqlx::Error> {
366 sqlx::query(
367 r#"
368 UPDATE orders
369 SET kind = ?, status = ?, amount = ?, min_amount = ?, max_amount = ?,
370 fiat_code = ?, fiat_amount = ?, payment_method = ?, premium = ?, trade_keys = ?,
371 counterparty_pubkey = ?, is_mine = ?, buyer_invoice = ?, request_id = ?, created_at = ?, expires_at = ?
372 WHERE id = ?
373 "#,
374 )
375 .bind(&self.kind)
376 .bind(&self.status)
377 .bind(self.amount)
378 .bind(self.min_amount)
379 .bind(self.max_amount)
380 .bind(&self.fiat_code)
381 .bind(self.fiat_amount)
382 .bind(&self.payment_method)
383 .bind(self.premium)
384 .bind(&self.trade_keys)
385 .bind(&self.counterparty_pubkey)
386 .bind(self.is_mine)
387 .bind(&self.buyer_invoice)
388 .bind(self.request_id)
389 .bind(self.created_at)
390 .bind(self.expires_at)
391 .bind(&self.id)
392 .execute(pool)
393 .await?
394 .rows_affected();
395 Ok(())
396 }
397
398 pub fn set_kind(&mut self, kind: String) -> &mut Self {
400 self.kind = Some(kind);
401 self
402 }
403
404 pub fn set_status(&mut self, status: String) -> &mut Self {
405 self.status = Some(status);
406 self
407 }
408
409 pub fn set_amount(&mut self, amount: i64) -> &mut Self {
410 self.amount = amount;
411 self
412 }
413
414 pub fn set_fiat_code(&mut self, fiat_code: String) -> &mut Self {
415 self.fiat_code = fiat_code;
416 self
417 }
418
419 pub fn set_min_amount(&mut self, min_amount: i64) -> &mut Self {
420 self.min_amount = Some(min_amount);
421 self
422 }
423
424 pub fn set_max_amount(&mut self, max_amount: i64) -> &mut Self {
425 self.max_amount = Some(max_amount);
426 self
427 }
428
429 pub fn set_fiat_amount(&mut self, fiat_amount: i64) -> &mut Self {
430 self.fiat_amount = fiat_amount;
431 self
432 }
433
434 pub fn set_payment_method(&mut self, payment_method: String) -> &mut Self {
435 self.payment_method = payment_method;
436 self
437 }
438
439 pub fn set_premium(&mut self, premium: i64) -> &mut Self {
440 self.premium = premium;
441 self
442 }
443
444 pub fn set_counterparty_pubkey(&mut self, counterparty_pubkey: String) -> &mut Self {
445 self.counterparty_pubkey = Some(counterparty_pubkey);
446 self
447 }
448
449 pub fn set_trade_keys(&mut self, trade_keys: String) -> &mut Self {
450 self.trade_keys = Some(trade_keys);
451 self
452 }
453
454 pub fn set_is_mine(&mut self, is_mine: bool) -> &mut Self {
455 self.is_mine = Some(is_mine);
456 self
457 }
458
459 pub async fn save(&self, pool: &SqlitePool) -> Result<()> {
461 if let Some(ref id) = self.id {
463 sqlx::query(
464 r#"
465 UPDATE orders
466 SET kind = ?, status = ?, amount = ?, fiat_code = ?, min_amount = ?, max_amount = ?,
467 fiat_amount = ?, payment_method = ?, premium = ?, trade_keys = ?, counterparty_pubkey = ?,
468 is_mine = ?, buyer_invoice = ?, expires_at = ?
469 WHERE id = ?
470 "#,
471 )
472 .bind(&self.kind)
473 .bind(&self.status)
474 .bind(self.amount)
475 .bind(&self.fiat_code)
476 .bind(self.min_amount)
477 .bind(self.max_amount)
478 .bind(self.fiat_amount)
479 .bind(&self.payment_method)
480 .bind(self.premium)
481 .bind(&self.trade_keys)
482 .bind(&self.counterparty_pubkey)
483 .bind(self.is_mine)
484 .bind(&self.buyer_invoice)
485 .bind(self.expires_at)
486 .bind(id)
487 .execute(pool)
488 .await?;
489
490 println!("Order with id {} updated in the database.", id);
491 } else {
492 return Err(anyhow::anyhow!("Order must have an ID to be updated."));
493 }
494
495 Ok(())
496 }
497
498 pub async fn save_new_id(
499 pool: &SqlitePool,
500 id: String,
501 new_id: String,
502 ) -> anyhow::Result<bool> {
503 let rows_affected = sqlx::query(
504 r#"
505 UPDATE orders
506 SET id = ?
507 WHERE id = ?
508 "#,
509 )
510 .bind(&new_id)
511 .bind(&id)
512 .execute(pool)
513 .await?
514 .rows_affected();
515
516 Ok(rows_affected > 0)
517 }
518
519 pub async fn get_by_id(pool: &SqlitePool, id: &str) -> Result<Order> {
520 let order = sqlx::query_as::<_, Order>(
521 r#"
522 SELECT * FROM orders WHERE id = ?
523 LIMIT 1
524 "#,
525 )
526 .bind(id)
527 .fetch_one(pool)
528 .await?;
529
530 if order.id.is_none() {
531 return Err(anyhow::anyhow!("Order not found"));
532 }
533
534 Ok(order)
535 }
536
537 pub async fn get_all_trade_keys(pool: &SqlitePool) -> Result<Vec<String>> {
538 let trade_keys: Vec<String> = sqlx::query_scalar::<_, Option<String>>(
539 "SELECT DISTINCT trade_keys FROM orders WHERE trade_keys IS NOT NULL",
540 )
541 .fetch_all(pool)
542 .await?
543 .into_iter()
544 .flatten()
545 .collect();
546
547 Ok(trade_keys)
548 }
549
550 pub async fn delete_by_id(pool: &SqlitePool, id: &str) -> Result<bool> {
551 let rows_affected = sqlx::query(
552 r#"
553 DELETE FROM orders
554 WHERE id = ?
555 "#,
556 )
557 .bind(id)
558 .execute(pool)
559 .await?
560 .rows_affected();
561
562 Ok(rows_affected > 0)
563 }
564}