Skip to main content

rustbasic_core/
session_manager.rs

1use crate::sql::AnyPool;
2
3/// Mengganti placeholder Postgres ($1, $2, ...) dengan placeholder MySQL (?).
4/// Implementasi manual tanpa regex — iterasi karakter satu kali (O(n)).
5fn replace_postgres_placeholders(sql: &str) -> String {
6    let mut result = String::with_capacity(sql.len());
7    let mut chars = sql.chars().peekable();
8
9    while let Some(ch) = chars.next() {
10        if ch == '$' {
11            // Konsumsi digit setelah '$' (jika ada)
12            let has_digit = chars.peek().map_or(false, |c| c.is_ascii_digit());
13            if has_digit {
14                while chars.peek().map_or(false, |c| c.is_ascii_digit()) {
15                    chars.next();
16                }
17                result.push('?');
18            } else {
19                result.push('$');
20            }
21        } else {
22            result.push(ch);
23        }
24    }
25
26    result
27}
28
29#[derive(Clone, Debug)]
30pub struct RustBasicSessionStore {
31    pub pool: AnyPool,
32}
33
34impl RustBasicSessionStore {
35    pub fn new(pool: AnyPool) -> Self {
36        Self { pool }
37    }
38
39    async fn get_placeholder_query(&self, sql: &str) -> String {
40        let is_mysql = if let Ok(conn) = self.pool.acquire().await {
41            conn.backend_name() == "MySQL"
42        } else {
43            false
44        };
45
46        if is_mysql {
47            replace_postgres_placeholders(sql)
48        } else {
49            sql.to_string()
50        }
51    }
52
53    pub async fn load(&self, id: &str) -> Option<String> {
54        let raw_query = "SELECT payload FROM sessions WHERE id = $1 AND last_activity > $2";
55        let query = self.get_placeholder_query(raw_query).await;
56        let now = crate::chrono::Utc::now().timestamp();
57        
58        let row_opt = crate::sql::query(&query)
59            .bind(id)
60            .bind(now)
61            .fetch_optional(&self.pool)
62            .await
63            .ok()
64            .flatten();
65
66        if let Some(row) = row_opt {
67            if let Ok(s) = row.try_get::<String, _>(0) {
68                return Some(s);
69            }
70            if let Ok(bytes) = row.try_get::<Vec<u8>, _>(0) {
71                if let Ok(s) = String::from_utf8(bytes) {
72                    return Some(s);
73                }
74            }
75        }
76        None
77    }
78
79    pub async fn store(&self, id: &str, session_json: &str, ip: &str) {
80        let raw_delete_query = "DELETE FROM sessions WHERE id = $1";
81        let delete_query = self.get_placeholder_query(raw_delete_query).await;
82        let _ = crate::sql::query(&delete_query).bind(id).execute(&self.pool).await;
83
84        let raw_insert_query = "INSERT INTO sessions (id, payload, last_activity, ip_address) VALUES ($1, $2, $3, $4)";
85        let insert_query = self.get_placeholder_query(raw_insert_query).await;
86        let expires = crate::chrono::Utc::now().timestamp() + 14 * 24 * 60 * 60; // 14 hari
87
88        let _ = crate::sql::query(&insert_query)
89            .bind(id)
90            .bind(session_json)
91            .bind(expires)
92            .bind(ip)
93            .execute(&self.pool)
94            .await;
95    }
96}