peillute/
db.rs

1//! Database management for the Peillute application
2//!
3//! This module handles all database operations, including user management,
4//! transaction processing, and state synchronization. It uses SQLite as the
5//! underlying database engine.
6
7/// Represents a transaction in the system
8#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
9pub struct Transaction {
10    /// Source user of the transaction
11    pub from_user: String,
12    /// Destination user of the transaction
13    pub to_user: String,
14    /// Transaction amount
15    pub amount: f64,
16    /// Lamport timestamp of the transaction
17    pub lamport_time: i64,
18    /// ID of the node that created the transaction
19    pub source_node: String,
20    /// Optional message associated with the transaction
21    pub optional_msg: Option<String>,
22    /// Vector clock state at the time of the transaction
23    pub vector_clock: std::collections::HashMap<String, i64>,
24}
25
26#[allow(unused_imports)]
27use clap::Parser;
28#[cfg(feature = "server")]
29lazy_static::lazy_static! {
30    pub static ref DB_CONN: std::sync::Mutex<rusqlite::Connection> =
31        std::sync::Mutex::new(rusqlite::Connection::open(format!("peillute_{}.db", super::Args::parse().cli_db_id)).unwrap());
32}
33
34#[cfg(feature = "server")]
35/// Special value representing a null user
36const NULL: &str = "NULL";
37
38#[cfg(feature = "server")]
39/// Initializes the database schema
40pub fn init_db() -> rusqlite::Result<()> {
41    {
42        let conn = DB_CONN.lock().unwrap();
43
44        // Create VectorClock table for storing vector clock states
45        conn.execute(
46            "CREATE TABLE IF NOT EXISTS VectorClock (
47            id INTEGER PRIMARY KEY AUTOINCREMENT
48        );",
49            [],
50        )?;
51
52        // Create VectorClockEntry table for storing individual vector clock entries
53        conn.execute(
54            "CREATE TABLE IF NOT EXISTS VectorClockEntry (
55                vector_clock_id INTEGER,
56                site_id TEXT,
57                value INTEGER NOT NULL,
58                PRIMARY KEY(vector_clock_id, site_id),
59                FOREIGN KEY(vector_clock_id) REFERENCES VectorClock(id) ON DELETE CASCADE
60            );
61            ",
62            [],
63        )?;
64
65        // Create User table for storing user accounts
66        conn.execute(
67            "CREATE TABLE IF NOT EXISTS User (
68            unique_name TEXT PRIMARY KEY,
69            solde FLOAT NOT NULL
70        )",
71            [],
72        )?;
73
74        // Create Transactions table for storing transaction history
75        conn.execute(
76            "CREATE TABLE IF NOT EXISTS Transactions (
77                from_user TEXT,
78                to_user TEXT NOT NULL,
79                amount FLOAT NOT NULL,
80                lamport_time INTEGER NOT NULL,
81                vector_clock_id INTEGER NOT NULL,
82                source_node TEXT NOT NULL,
83                optional_msg TEXT,
84                FOREIGN KEY(from_user) REFERENCES User(unique_name),
85                FOREIGN KEY(to_user) REFERENCES User(unique_name),
86                FOREIGN KEY(vector_clock_id) REFERENCES VectorClock(id),
87                PRIMARY KEY(lamport_time, source_node)
88            );",
89            [],
90        )?;
91
92        conn.execute(
93            "CREATE TABLE IF NOT EXISTS LocalState (
94            site_id TEXT PRIMARY KEY,
95            lamport_time INTEGER NOT NULL,
96            vector_clock_id INTEGER NOT NULL,
97            FOREIGN KEY(vector_clock_id) REFERENCES VectorClock(id)
98        );",
99            [],
100        )?;
101    }
102
103    log::debug!("Database initialized successfully.");
104    Ok(())
105}
106
107#[cfg(feature = "server")]
108/// Update the local state of the site
109pub fn update_local_state(site_id: &str, clock: crate::clock::Clock) -> rusqlite::Result<()> {
110    use rusqlite::params;
111
112    let lamport_time = clock.get_lamport();
113    let vc_clock = clock.get_vector_clock_map();
114
115    let conn = DB_CONN.lock().unwrap();
116    conn.execute("INSERT INTO VectorClock DEFAULT VALUES", [])?;
117    let vector_clock_id = conn.last_insert_rowid();
118
119    let mut stmt = conn.prepare(
120        "INSERT INTO VectorClockEntry (vector_clock_id, site_id, value) VALUES (?1, ?2, ?3)",
121    )?;
122    for (site, value) in vc_clock.iter() {
123        stmt.execute(params![vector_clock_id, site, value])?;
124    }
125    conn.execute(
126        "INSERT OR REPLACE INTO LocalState (site_id, lamport_time, vector_clock_id)
127        VALUES (?1, ?2, ?3)",
128        params![site_id, lamport_time, vector_clock_id],
129    )?;
130    Ok(())
131}
132
133#[cfg(feature = "server")]
134/// Update the database with a snapshot
135pub fn update_db_with_snapshot(
136    snapshot: &crate::snapshot::GlobalSnapshot,
137    vector_clock: &std::collections::HashMap<String, i64>,
138) {
139    log::info!("Applying snapshot to database");
140
141    if snapshot.missing.is_empty() {
142        log::info!("No missing transactions, nothing to do");
143        return;
144    }
145
146    // sort tsx actions by lamport time
147    let mut sorted_txs: Vec<_> = snapshot
148        .missing
149        .values()
150        .flat_map(|txs| txs.iter())
151        .collect();
152    sorted_txs.sort_by_key(|tx| tx.lamport_time);
153
154    for tx in sorted_txs {
155        let optional_msg = "";
156
157        let _ = crate::db::create_transaction(
158            &tx.from_user,
159            &tx.to_user,
160            (tx.amount_in_cent as f64) / 100.0,
161            &tx.lamport_time,
162            &tx.source_node,
163            &optional_msg,
164            vector_clock,
165        );
166    }
167}
168
169#[cfg(feature = "server")]
170/// Get the local state of the site
171pub fn get_local_state() -> rusqlite::Result<(String, crate::clock::Clock)> {
172    use rusqlite::params;
173    let conn = DB_CONN.lock().unwrap();
174    let mut stmt =
175        conn.prepare("SELECT site_id, lamport_time, vector_clock_id FROM LocalState LIMIT 1")?;
176
177    let row = stmt.query_row([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)));
178
179    let (site_id, lamport_time, vector_clock_id): (String, i64, i64) = match row {
180        Ok(data) => data,
181        Err(rusqlite::Error::QueryReturnedNoRows) => {
182            return Err(rusqlite::Error::QueryReturnedNoRows);
183        }
184        Err(e) => return Err(e),
185    };
186
187    let mut clock_map = std::collections::HashMap::new();
188    let mut vc_stmt =
189        conn.prepare("SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1")?;
190    let mut rows = vc_stmt.query(params![vector_clock_id])?;
191    while let Some(vc_row) = rows.next()? {
192        let site: String = vc_row.get(0)?;
193        let value: i64 = vc_row.get(1)?;
194        clock_map.insert(site, value);
195    }
196
197    let c = crate::clock::Clock::from_parts(lamport_time, clock_map);
198
199    Ok((site_id, c))
200}
201
202#[cfg(feature = "server")]
203/// Checks if the database has been initialized
204pub fn is_database_initialized() -> rusqlite::Result<bool> {
205    {
206        let conn = DB_CONN.lock().unwrap();
207        let mut stmt = conn.prepare(
208            "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'Transactions')",
209        )?;
210        let exists: bool = stmt.query_row([], |row| row.get(0))?;
211        Ok(exists)
212    }
213}
214
215#[cfg(feature = "server")]
216/// Check if a transaction exists in the database
217pub fn transaction_exists(lamport_time: i64, source_node: &str) -> rusqlite::Result<bool> {
218    use rusqlite::params;
219    {
220        let conn = DB_CONN.lock().unwrap();
221        let mut stmt = conn.prepare(
222            "SELECT EXISTS(SELECT 1 FROM Transactions WHERE lamport_time = ?1 AND source_node = ?2)",
223        )?;
224        let exists: bool = stmt.query_row(params![lamport_time, source_node], |row| row.get(0))?;
225        Ok(exists)
226    }
227}
228
229#[cfg(feature = "server")]
230/// Checks if a user exists in the database
231pub fn user_exists(name: &str) -> rusqlite::Result<bool> {
232    {
233        use rusqlite::params;
234        let conn = DB_CONN.lock().unwrap();
235        let mut stmt = conn.prepare("SELECT EXISTS(SELECT 1 FROM User WHERE unique_name = ?1)")?;
236        let exists: bool = stmt.query_row(params![name], |row| row.get(0))?;
237        Ok(exists)
238    }
239}
240
241#[cfg(feature = "server")]
242/// Creates a new user with zero balance
243pub fn create_user(unique_name: &str) -> rusqlite::Result<()> {
244    use rusqlite::params;
245    if user_exists(unique_name)? {
246        log::warn!("User '{}' already exists.", unique_name);
247        return Ok(());
248    }
249
250    {
251        log::debug!("Ajout de l'utilisateur {}", unique_name);
252        let conn = DB_CONN.lock().unwrap();
253        conn.execute(
254            "INSERT INTO User (unique_name, solde) VALUES (?1, 0)",
255            params![unique_name],
256        )?;
257        Ok(())
258    }
259}
260
261#[cfg(feature = "server")]
262/// Deletes a user from the database
263pub fn delete_user(name: &str) -> rusqlite::Result<()> {
264    use rusqlite::params;
265    if !user_exists(name)? {
266        let err = rusqlite::Error::SqliteFailure(
267            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
268            Some(format!("User '{}' does not exist.", name).into()),
269        );
270
271        log::error!("User '{}' does not exist.", name);
272        return Err(err);
273    }
274    {
275        let conn = DB_CONN.lock().unwrap();
276        conn.execute("DELETE FROM User WHERE unique_name = ?1", params![name])?;
277        Ok(())
278    }
279}
280
281#[cfg(feature = "server")]
282/// Calculates the current balance for a user
283pub fn calculate_solde(name: &str) -> rusqlite::Result<f64> {
284    {
285        use rusqlite::params;
286        let conn = DB_CONN.lock().unwrap();
287        let mut stmt = conn.prepare(
288            "SELECT
289            IFNULL((SELECT SUM(amount) FROM Transactions WHERE to_user = ?1), 0) -
290            IFNULL((SELECT SUM(amount) FROM Transactions WHERE from_user = ?1), 0)
291        AS balance",
292        )?;
293        stmt.query_row(params![name], |row| row.get(0))
294    }
295}
296
297#[cfg(feature = "server")]
298/// Updates the stored balance for a user
299pub fn update_solde(name: &str) -> rusqlite::Result<()> {
300    use rusqlite::params;
301
302    if !user_exists(name)? {
303        let err = rusqlite::Error::SqliteFailure(
304            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
305            Some(format!("User '{}' does not exist.", name).into()),
306        );
307
308        return Err(err);
309    }
310    let solde = calculate_solde(name)?;
311    {
312        let conn = DB_CONN.lock().unwrap();
313        conn.execute(
314            "UPDATE User SET solde = ?1 WHERE unique_name = ?2",
315            params![solde, name],
316        )?;
317        log::debug!("Updated solde for {} to {}", name, solde);
318        Ok(())
319    }
320}
321
322#[cfg(feature = "server")]
323/// Ensures a user exists, creating it if necessary
324pub fn ensure_user(name: &str) -> rusqlite::Result<()> {
325    if name != NULL && !user_exists(name)? {
326        create_user(name)?;
327    }
328    Ok(())
329}
330
331#[cfg(feature = "server")]
332/// Creates a new transaction between users
333pub fn create_transaction(
334    from_user: &str,
335    to_user: &str,
336    amount: f64,
337    lamport_time: &i64,
338    source_node: &str,
339    optional_msg: &str,
340    vector_clock: &std::collections::HashMap<String, i64>,
341) -> rusqlite::Result<()> {
342    use rusqlite::params;
343    if from_user != NULL && calculate_solde(from_user)? < amount {
344        let err = rusqlite::Error::SqliteFailure(
345            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
346            Some(
347                format!(
348                    "Insufficient funds: '{}' has less than {}.",
349                    from_user, amount
350                )
351                .into(),
352            ),
353        );
354
355        log::error!(
356            "Insufficient funds: '{}' has less than {}.",
357            from_user,
358            amount
359        );
360        return Err(err);
361    }
362
363    ensure_user(from_user)?;
364    ensure_user(to_user)?;
365
366    log::debug!(
367        "Creating transaction from {} to {} with amount {}",
368        from_user,
369        to_user,
370        amount
371    );
372
373    {
374        let conn = DB_CONN.lock().unwrap();
375        conn.execute("INSERT INTO VectorClock DEFAULT VALUES", [])?;
376        let vector_clock_id = conn.last_insert_rowid();
377
378        let mut stmt = conn.prepare(
379            "INSERT INTO VectorClockEntry (vector_clock_id, site_id, value) VALUES (?1, ?2, ?3)",
380        )?;
381        for (site_id, value) in vector_clock.iter() {
382            stmt.execute(params![vector_clock_id, site_id, value])?;
383        }
384
385        conn.execute(
386        "INSERT INTO Transactions (from_user, to_user, amount, lamport_time, vector_clock_id, source_node, optional_msg)
387        VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
388        params![
389            from_user,
390            to_user,
391            amount,
392            lamport_time,
393            vector_clock_id,
394            source_node,
395            optional_msg
396        ],
397    )?;
398    }
399
400    if from_user != NULL {
401        update_solde(from_user)?;
402    }
403    if to_user != NULL {
404        update_solde(to_user)?;
405    }
406
407    Ok(())
408}
409
410#[cfg(feature = "server")]
411pub fn deposit(
412    user: &str,
413    amount: f64,
414    lamport_time: &i64,
415    source_node: &str,
416    vector_clock: &std::collections::HashMap<String, i64>,
417) -> rusqlite::Result<()> {
418    if !user_exists(user)? {
419        let err = rusqlite::Error::SqliteFailure(
420            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
421            Some(format!("Unknown User: {}", user).into()),
422        );
423
424        log::error!("Unknown User: {}", user);
425        return Err(err);
426    }
427
428    if amount < 0.0 {
429        let err = rusqlite::Error::SqliteFailure(
430            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
431            Some(format!("Negative deposit amount: {}", amount).into()),
432        );
433
434        log::error!("Negative deposit amount: {}", amount);
435        return Err(err);
436    }
437
438    log::debug!("Depositing {} to {}", amount, user);
439
440    create_transaction(
441        NULL,
442        user,
443        amount,
444        lamport_time,
445        source_node,
446        "Deposit",
447        vector_clock,
448    )
449}
450
451#[cfg(feature = "server")]
452pub fn withdraw(
453    user: &str,
454    amount: f64,
455    lamport_time: &i64,
456    source_node: &str,
457    vector_clock: &std::collections::HashMap<String, i64>,
458) -> rusqlite::Result<()> {
459    if amount < 0.0 {
460        let err = rusqlite::Error::SqliteFailure(
461            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
462            Some(format!("Negative withdrawal amount: {}", amount).into()),
463        );
464        log::error!("Negative withdrawal amount: {}", amount);
465        return Err(err);
466    }
467    if !user_exists(user)? {
468        let err = rusqlite::Error::SqliteFailure(
469            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
470            Some(format!("Unknown user: {}", user).into()),
471        );
472        log::error!("Unknown user: {}", user);
473        return Err(err);
474    }
475    if calculate_solde(user)? < amount {
476        let err = rusqlite::Error::SqliteFailure(
477            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
478            Some(format!("User {} not enough money", user).into()),
479        );
480        log::error!("User {} not enough money", user);
481        return Err(err);
482    }
483
484    log::debug!("Withdrawing {} from {}", amount, user);
485
486    create_transaction(
487        user,
488        NULL,
489        amount,
490        lamport_time,
491        source_node,
492        "Withdraw",
493        vector_clock,
494    )
495}
496
497#[cfg(feature = "server")]
498pub fn has_been_refunded(transac_time: i64, node: &str) -> rusqlite::Result<bool> {
499    use rusqlite::params;
500    {
501        let conn = DB_CONN.lock().unwrap();
502        let mut stmt =
503            conn.prepare("SELECT EXISTS(SELECT 1 FROM Transactions WHERE optional_msg = ?1)")?;
504
505        let optional_msg = format!("Refund transaction {}-{}", node, transac_time);
506        let exists: bool = stmt.query_row(params![optional_msg], |row| row.get(0))?;
507
508        Ok(exists)
509    }
510}
511
512#[cfg(feature = "server")]
513pub fn refund_transaction(
514    transac_time: i64,
515    node: &str,
516    lamport_time: &i64,
517    source_node: &str,
518    vector_clock: &std::collections::HashMap<String, i64>,
519) -> rusqlite::Result<()> {
520    if let Some(tx) = get_transaction(transac_time, node)? {
521        if calculate_solde(&tx.to_user)? < tx.amount {
522            let err = rusqlite::Error::SqliteFailure(
523                rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
524                Some(format!("User {} has not enough money to give back", &tx.to_user).into()),
525            );
526            log::error!("User {} has not enough money to give back", &tx.to_user);
527            return Err(err);
528        }
529
530        if tx.optional_msg.is_some() && tx.optional_msg.unwrap().starts_with("Refund transaction") {
531            let err = rusqlite::Error::SqliteFailure(
532                rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
533                Some(
534                    format!(
535                        "Transaction {}-{} is a refund transaction",
536                        node, transac_time
537                    )
538                    .into(),
539                ),
540            );
541            log::error!(
542                "Transaction {}-{} is a refund transaction",
543                node,
544                transac_time
545            );
546            return Err(err);
547        }
548
549        if has_been_refunded(transac_time, node)? {
550            let err = rusqlite::Error::SqliteFailure(
551                rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
552                Some(format!("Transaction {}-{} already refunded", node, transac_time).into()),
553            );
554            log::error!("Transaction {}-{} already refunded", node, transac_time);
555            return Err(err);
556        }
557
558        create_transaction(
559            &tx.to_user,
560            &tx.from_user,
561            tx.amount,
562            lamport_time,
563            source_node,
564            &format!("Refund transaction {}-{}", node, transac_time),
565            vector_clock,
566        )?;
567    } else {
568        let err = rusqlite::Error::SqliteFailure(
569            rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
570            Some(
571                format!(
572                    "No transaction found at time {} from node {}",
573                    transac_time, node
574                )
575                .into(),
576            ),
577        );
578
579        log::error!(
580            "No transaction found at time {} from node {}",
581            transac_time,
582            node
583        );
584        return Err(err);
585    }
586    Ok(())
587}
588
589#[cfg(feature = "server")]
590pub fn get_transaction(transac_time: i64, node: &str) -> rusqlite::Result<Option<Transaction>> {
591    use rusqlite::params;
592    {
593        let conn = DB_CONN.lock().unwrap();
594        let mut stmt = conn.prepare(
595            "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id
596        FROM Transactions WHERE lamport_time = ?1 AND source_node = ?2",
597        )?;
598
599        match stmt.query_row(params![transac_time, node], |row| {
600            let from_user: String = row.get(0)?;
601            let to_user: String = row.get(1)?;
602            let amount: f64 = row.get(2)?;
603            let lamport_time: i64 = row.get(3)?;
604            let source_node: String = row.get(4)?;
605            let optional_msg: Option<String> = row.get(5)?;
606            let vector_clock_id: i64 = row.get(6)?;
607
608            let mut clock_map = std::collections::HashMap::new();
609            let mut vc_stmt = conn.prepare(
610                "SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1",
611            )?;
612            let mut rows = vc_stmt.query(params![vector_clock_id])?;
613            while let Some(vc_row) = rows.next()? {
614                let site_id: String = vc_row.get(0)?;
615                let value: i64 = vc_row.get(1)?;
616                clock_map.insert(site_id, value);
617            }
618
619            Ok(Transaction {
620                from_user,
621                to_user,
622                amount,
623                lamport_time,
624                source_node,
625                optional_msg,
626                vector_clock: clock_map,
627            })
628        }) {
629            Ok(tx) => Ok(Some(tx)),
630            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
631            Err(e) => Err(e),
632        }
633    }
634}
635
636#[cfg(feature = "server")]
637pub fn print_users() -> rusqlite::Result<()> {
638    {
639        let conn = DB_CONN.lock().unwrap();
640        let mut stmt = conn.prepare("SELECT unique_name, solde FROM User")?;
641        let users = stmt.query_map([], |row| {
642            Ok((row.get::<_, String>(0)?, row.get::<_, f64>(1)?))
643        })?;
644
645        println!("-- Users --");
646        for user in users {
647            let (name, solde) = user?;
648            println!("{}: {:.2}", name, solde);
649        }
650        Ok(())
651    }
652}
653
654#[cfg(feature = "server")]
655pub fn get_users() -> rusqlite::Result<Vec<String>> {
656    {
657        let conn = DB_CONN.lock().unwrap();
658        let mut stmt = conn.prepare("SELECT unique_name FROM User")?;
659        let users = stmt.query_map([], |row| Ok(row.get::<_, String>(0)?))?;
660        let mut users_vec = Vec::new();
661        for user in users {
662            users_vec.push(user?);
663        }
664        Ok(users_vec)
665    }
666}
667
668#[cfg(feature = "server")]
669pub fn print_transactions() -> rusqlite::Result<()> {
670    {
671        let conn = DB_CONN.lock().unwrap();
672        let mut stmt = conn.prepare(
673            "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id FROM Transactions",
674        )?;
675        let txs = stmt.query_map([], |row| {
676            Ok((
677                row.get::<_, String>(0)?,
678                row.get::<_, String>(1)?,
679                row.get::<_, f64>(2)?,
680                row.get::<_, i64>(3)?,
681                row.get::<_, String>(4)?,
682                row.get::<_, Option<String>>(5)?,
683                row.get::<_, i64>(6)?,
684            ))
685        })?;
686
687        println!("📜 -- Transactions --");
688        println!(
689            "┌─────────────────┬─────────────────┬────────────┬────────────┬─────────────────┬──────────────────────┬──────────────────────┐"
690        );
691        println!(
692            "│ {:<15} │ {:<15} │ {:<10} │ {:<10} │ {:<15} │ {:<20} │ {:<20} │",
693            "From", "To", "Amount", "Time", "Node", "Message", "Vector Clock"
694        );
695        println!(
696            "├─────────────────┼─────────────────┼────────────┼────────────┼─────────────────┼──────────────────────┼──────────────────────┤"
697        );
698
699        for tx in txs {
700            let (from, to, amount, time, node, msg, vector_clock_id) = tx?;
701            let mut clock_map = std::collections::HashMap::new();
702            let mut vc_stmt = conn.prepare(
703                "SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1",
704            )?;
705            let mut rows = vc_stmt.query(rusqlite::params![vector_clock_id])?;
706            while let Some(vc_row) = rows.next()? {
707                let site_id: String = vc_row.get(0)?;
708                let value: i64 = vc_row.get(1)?;
709                clock_map.insert(site_id, value);
710            }
711
712            println!(
713                "│ {:<15} │ {:<15} │ {:<10.2} │ {:<10} │ {:<15} │ {:<20} │ {:<20?} │",
714                from,
715                to,
716                amount,
717                time,
718                node,
719                msg.unwrap_or_default(),
720                clock_map
721            );
722        }
723
724        println!(
725            "└─────────────────┴─────────────────┴────────────┴────────────┴─────────────────┴──────────────────────┴──────────────────────┘"
726        );
727        Ok(())
728    }
729}
730
731#[cfg(feature = "server")]
732pub fn print_transaction_for_user(name: &str) -> rusqlite::Result<()> {
733    use rusqlite::params;
734    {
735        let conn = DB_CONN.lock().unwrap();
736        let mut stmt = conn.prepare(
737            "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id
738        FROM Transactions WHERE from_user = ?1 OR to_user = ?1",
739        )?;
740
741        let txs = stmt.query_map(params![name], |row| {
742            Ok((
743                row.get::<_, String>(0)?,
744                row.get::<_, String>(1)?,
745                row.get::<_, f64>(2)?,
746                row.get::<_, i64>(3)?,
747                row.get::<_, String>(4)?,
748                row.get::<_, Option<String>>(5)?,
749                row.get::<_, i64>(6)?,
750            ))
751        })?;
752
753        println!("📜 -- Transactions for user {} --", name);
754        println!(
755            "┌─────────────────┬─────────────────┬────────────┬────────────┬─────────────────┬──────────────────────┬──────────────────────┐"
756        );
757        println!(
758            "│ {:<15} │ {:<15} │ {:<10} │ {:<10} │ {:<15} │ {:<20} │ {:<20} │",
759            "From", "To", "Amount", "Time", "Node", "Message", "Vector Clock"
760        );
761        println!(
762            "├─────────────────┼─────────────────┼────────────┼────────────┼─────────────────┼──────────────────────┼──────────────────────┤"
763        );
764
765        for tx in txs {
766            let (from, to, amount, time, node, msg, vector_clock_id) = tx?;
767            let mut clock_map = std::collections::HashMap::new();
768            let mut vc_stmt = conn.prepare(
769                "SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1",
770            )?;
771            let mut rows = vc_stmt.query(rusqlite::params![vector_clock_id])?;
772            while let Some(vc_row) = rows.next()? {
773                let site_id: String = vc_row.get(0)?;
774                let value: i64 = vc_row.get(1)?;
775                clock_map.insert(site_id, value);
776            }
777            println!(
778                "│ {:<15} │ {:<15} │ {:<10.2} │ {:<10} │ {:<15} │ {:<20} │ {:<20?} │",
779                from,
780                to,
781                amount,
782                time,
783                node,
784                msg.unwrap_or_default(),
785                clock_map
786            );
787        }
788
789        println!(
790            "└─────────────────┴─────────────────┴────────────┴────────────┴─────────────────┴──────────────────────┴──────────────────────┘"
791        );
792        Ok(())
793    }
794}
795
796#[cfg(feature = "server")]
797pub fn get_transactions_for_user(name: &str) -> rusqlite::Result<Vec<Transaction>> {
798    use rusqlite::params;
799    {
800        let conn = DB_CONN.lock().unwrap();
801        let mut stmt = conn.prepare(
802            "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id
803        FROM Transactions WHERE from_user = ?1 OR to_user = ?1",
804        )?;
805
806        let txs = stmt.query_map(params![name], |row| {
807            Ok((
808                row.get::<_, String>(0)?,
809                row.get::<_, String>(1)?,
810                row.get::<_, f64>(2)?,
811                row.get::<_, i64>(3)?,
812                row.get::<_, String>(4)?,
813                row.get::<_, Option<String>>(5)?,
814                row.get::<_, i64>(6)?,
815            ))
816        })?;
817
818        let mut txs_vec = Vec::new();
819        for tx in txs {
820            let (from, to, amount, time, node, msg, vector_clock_id) = tx?;
821            let mut clock_map = std::collections::HashMap::new();
822            let mut vc_stmt = conn.prepare(
823                "SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1",
824            )?;
825            let mut rows = vc_stmt.query(rusqlite::params![vector_clock_id])?;
826            while let Some(vc_row) = rows.next()? {
827                let site_id: String = vc_row.get(0)?;
828                let value: i64 = vc_row.get(1)?;
829                clock_map.insert(site_id, value);
830            }
831
832            txs_vec.push(Transaction {
833                from_user: from,
834                to_user: to,
835                amount: amount,
836                lamport_time: time,
837                source_node: node,
838                optional_msg: msg,
839                vector_clock: clock_map,
840            });
841        }
842        Ok(txs_vec)
843    }
844}
845
846#[cfg(feature = "server")]
847pub fn get_local_transaction_log() -> rusqlite::Result<Vec<Transaction>> {
848    let conn = DB_CONN.lock().unwrap();
849    let mut stmt = conn.prepare(
850    "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id
851        FROM Transactions")?;
852    let rows = stmt.query_map([], |row| {
853        Ok(Transaction {
854            from_user: row.get(0)?,
855            to_user: row.get(1)?,
856            amount: row.get(2)?,
857            lamport_time: row.get(3)?,
858            source_node: row.get(4)?,
859            optional_msg: row.get(5)?,
860            vector_clock: std::collections::HashMap::new(),
861        })
862    })?;
863
864    let mut out = Vec::new();
865    for r in rows {
866        out.push(r?);
867    }
868    Ok(out)
869}