src/db.rs 3.4 K raw
1
use rand::RngExt;
2
use rusqlite::{Connection, params};
3
use serde::{Deserialize, Serialize};
4
use std::fmt;
5
use std::sync::{Arc, Mutex};
6
7
pub type Db = Arc<Mutex<Connection>>;
8
9
#[derive(Debug)]
10
pub enum DbError {
11
    Sqlite(rusqlite::Error),
12
    LockPoisoned,
13
}
14
15
impl fmt::Display for DbError {
16
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17
        match self {
18
            DbError::Sqlite(e) => write!(f, "Database error: {}", e),
19
            DbError::LockPoisoned => write!(f, "Database lock poisoned"),
20
        }
21
    }
22
}
23
24
impl std::error::Error for DbError {}
25
26
impl From<rusqlite::Error> for DbError {
27
    fn from(e: rusqlite::Error) -> Self {
28
        DbError::Sqlite(e)
29
    }
30
}
31
32
#[derive(Serialize, Deserialize)]
33
pub struct Snippet {
34
    pub id: i64,
35
    pub short_id: String,
36
    pub content: String,
37
    pub name: String,
38
}
39
40
const ALPHABET: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
41
42
fn generate_short_id() -> String {
43
    let mut rng = rand::rng();
44
    (0..10)
45
        .map(|_| ALPHABET[rng.random_range(0..ALPHABET.len())] as char)
46
        .collect()
47
}
48
49
pub fn init_db() -> Result<Db, DbError> {
50
    let conn = Connection::open("sipp.sqlite")?;
51
    conn.execute(
52
        "CREATE TABLE IF NOT EXISTS snippets (
53
            id INTEGER PRIMARY KEY AUTOINCREMENT,
54
            short_id TEXT NOT NULL UNIQUE,
55
            content TEXT NOT NULL,
56
            name TEXT NOT NULL
57
        )",
58
        [],
59
    )?;
60
    Ok(Arc::new(Mutex::new(conn)))
61
}
62
63
pub fn create_snippet(db: &Db, name: &str, content: &str) -> Result<Snippet, DbError> {
64
    let conn = db.lock().map_err(|_| DbError::LockPoisoned)?;
65
    let short_id = generate_short_id();
66
    conn.execute(
67
        "INSERT INTO snippets (short_id, content, name) VALUES (?1, ?2, ?3)",
68
        params![short_id, content, name],
69
    )?;
70
    let id = conn.last_insert_rowid();
71
    Ok(Snippet {
72
        id,
73
        short_id,
74
        content: content.to_string(),
75
        name: name.to_string(),
76
    })
77
}
78
79
pub fn get_snippet_by_short_id(db: &Db, short_id: &str) -> Result<Option<Snippet>, DbError> {
80
    let conn = db.lock().map_err(|_| DbError::LockPoisoned)?;
81
    match conn.query_row(
82
        "SELECT id, short_id, content, name FROM snippets WHERE short_id = ?1",
83
        params![short_id],
84
        |row| {
85
            Ok(Snippet {
86
                id: row.get(0)?,
87
                short_id: row.get(1)?,
88
                content: row.get(2)?,
89
                name: row.get(3)?,
90
            })
91
        },
92
    ) {
93
        Ok(snippet) => Ok(Some(snippet)),
94
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
95
        Err(e) => Err(DbError::Sqlite(e)),
96
    }
97
}
98
99
pub fn get_all_snippets(db: &Db) -> Result<Vec<Snippet>, DbError> {
100
    let conn = db.lock().map_err(|_| DbError::LockPoisoned)?;
101
    let mut stmt = conn
102
        .prepare("SELECT id, short_id, content, name FROM snippets ORDER BY id DESC")?;
103
    let snippets = stmt.query_map([], |row| {
104
        Ok(Snippet {
105
            id: row.get(0)?,
106
            short_id: row.get(1)?,
107
            content: row.get(2)?,
108
            name: row.get(3)?,
109
        })
110
    })?
111
    .filter_map(|r| r.ok())
112
    .collect();
113
    Ok(snippets)
114
}
115
116
pub fn delete_snippet_by_short_id(db: &Db, short_id: &str) -> Result<bool, DbError> {
117
    let conn = db.lock().map_err(|_| DbError::LockPoisoned)?;
118
    let rows_affected = conn.execute(
119
        "DELETE FROM snippets WHERE short_id = ?1",
120
        params![short_id],
121
    )?;
122
    Ok(rows_affected > 0)
123
}