Skip to main content

rustbasic_cli/
database.rs

1use std::fs;
2use base64::{Engine as _, engine::general_purpose};
3use sqlx::AnyPool;
4use colored::*;
5use rand::Rng;
6use regex::Regex;
7
8pub struct LocalDbConfig {
9    pub db_connection: String,
10    pub db_host: String,
11    pub db_port: u16,
12    pub db_database: String,
13    pub db_username: String,
14    pub db_password: String,
15}
16
17impl LocalDbConfig {
18    pub fn load() -> Self {
19        use std::env;
20        Self {
21            db_connection: env::var("DB_CONNECTION").unwrap_or_else(|_| "sqlite".to_string()),
22            db_host: env::var("DB_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
23            db_port: env::var("DB_PORT")
24                .unwrap_or_else(|_| "3306".to_string())
25                .parse()
26                .unwrap_or(3306),
27            db_database: env::var("DB_DATABASE").unwrap_or_else(|_| "rustbasic".to_string()),
28            db_username: env::var("DB_USERNAME").unwrap_or_else(|_| "root".to_string()),
29            db_password: env::var("DB_PASSWORD").unwrap_or_default(),
30        }
31    }
32
33    pub fn db_url(&self) -> String {
34        if self.db_connection == "mysql" {
35            format!(
36                "mysql://{}:{}@{}:{}/{}",
37                self.db_username, self.db_password, self.db_host, self.db_port, self.db_database
38            )
39        } else {
40            format!("sqlite:database/{}.sqlite?mode=rwc", self.db_database)
41        }
42    }
43}
44
45pub async fn connect() -> AnyPool {
46    sqlx::any::install_default_drivers();
47
48    let cfg = LocalDbConfig::load();
49    let db_url = cfg.db_url();
50
51    if cfg.db_connection != "mysql" {
52        let _ = std::fs::create_dir_all("database");
53    }
54
55    match AnyPool::connect(&db_url).await {
56        Ok(pool) => pool,
57        Err(e) => {
58            let err_msg = e.to_string();
59            if (err_msg.contains("1049") || err_msg.contains("Unknown database")) && cfg.db_connection == "mysql" {
60                println!("{}", "โš ๏ธ  Database tidak ditemukan. Mencoba membuat database baru...".yellow());
61                let root_url = format!(
62                    "mysql://{}:{}@{}:{}",
63                    cfg.db_username, cfg.db_password, cfg.db_host, cfg.db_port
64                );
65                if let Ok(pool) = sqlx::MySqlPool::connect(&root_url).await {
66                    let create_query = format!("CREATE DATABASE IF NOT EXISTS `{}`", cfg.db_database);
67                    if sqlx::query(&create_query).execute(&pool).await.is_ok() {
68                        println!("โœ… Database '{}' berhasil dibuat.", cfg.db_database.green());
69                        return AnyPool::connect(&db_url).await.expect("Gagal terhubung setelah membuat database");
70                    }
71                }
72            }
73            panic!("Gagal terhubung ke database: {:?}", e);
74        }
75    }
76}
77
78pub async fn clear_cache() {
79    println!("\n{}", "๐Ÿงน Cleaning Cache & Logs...".magenta().bold());
80
81    // 1. Clear Logs
82    let log_dir = "storage/logs";
83    if let Ok(entries) = fs::read_dir(log_dir) {
84        let mut count = 0;
85        for entry in entries.flatten() {
86            let path = entry.path();
87            if path.is_file() {
88                let _ = fs::OpenOptions::new()
89                    .write(true)
90                    .truncate(true)
91                    .open(&path);
92                count += 1;
93            }
94        }
95        println!("   {} Folder storage/logs telah dikosongkan. ({} file dibersihkan)", "โœ… Logs:".green(), count);
96    } else {
97        println!("   {} Folder storage/logs tidak ditemukan.", "โš ๏ธ  Logs:".yellow());
98    }
99
100    // 2. Clear Sessions in DB
101    let _ = dotenvy::dotenv();
102    let cfg = LocalDbConfig::load();
103    let pool = connect().await;
104
105    let sql = if cfg.db_connection == "mysql" {
106        "TRUNCATE TABLE sessions"
107    } else {
108        "DELETE FROM sessions"
109    };
110
111    match sqlx::query(sql).execute(&pool).await {
112        Ok(_) => println!("   {} Tabel sessions telah dikosongkan.", "โœ… Sessions:".green()),
113        Err(e) => println!("   {} Gagal membersihkan tabel sessions. ({})", "โŒ Error:".red(), e),
114    }
115
116    println!("\n{}", "โœจ Cache berhasil dibersihkan!".green().bold());
117}
118
119pub fn generate_app_key() {
120    println!("\n{}", "๐Ÿ”‘ Generating Application Key...".magenta().bold());
121
122    let mut key = [0u8; 32];
123    rand::rng().fill_bytes(&mut key);
124
125    let encoded = general_purpose::STANDARD.encode(key);
126    let key_str = format!("base64:{}", encoded);
127
128    let env_path = ".env";
129    match fs::read_to_string(env_path) {
130        Ok(content) => {
131            let re = Regex::new(r"(?m)^APP_KEY=.*").unwrap();
132            let new_content = if re.is_match(&content) {
133                re.replace(&content, &format!("APP_KEY={}", key_str)).to_string()
134            } else {
135                format!("{}\nAPP_KEY={}", content.trim_end(), key_str)
136            };
137
138            if let Err(e) = fs::write(env_path, new_content) {
139                println!("{} Gagal menulis ke file .env: {}", "โŒ Error:".red(), e);
140            } else {
141                println!("{} {}", "โœ… Application key set successfully:".green(), key_str.cyan());
142                println!("{}", "๐Ÿ’ก Pastikan untuk tidak membagikan APP_KEY ini ke publik!".dimmed());
143            }
144        }
145        Err(_) => {
146            println!("{} File .env tidak ditemukan.", "โŒ Error:".red());
147        }
148    }
149}
150
151pub async fn ensure_session() {
152    if !std::path::Path::new(".env").exists() {
153        return;
154    }
155
156    let _ = dotenvy::dotenv();
157    let cfg = LocalDbConfig::load();
158    let db_url = cfg.db_url();
159
160    if cfg.db_connection != "mysql" {
161        let _ = std::fs::create_dir_all("database");
162    }
163
164    sqlx::any::install_default_drivers();
165    let pool = match AnyPool::connect(&db_url).await {
166        Ok(p) => p,
167        Err(_) => return,
168    };
169
170    // Pastikan tabel sessions ada
171    let create_table_sql = if cfg.db_connection == "mysql" {
172        "CREATE TABLE IF NOT EXISTS sessions (
173            id VARCHAR(255) PRIMARY KEY,
174            payload VARCHAR(8000) NOT NULL,
175            last_activity BIGINT NOT NULL,
176            ip_address VARCHAR(45)
177        )"
178    } else {
179        "CREATE TABLE IF NOT EXISTS sessions (
180            id TEXT PRIMARY KEY,
181            payload TEXT NOT NULL,
182            last_activity BIGINT NOT NULL,
183            ip_address TEXT
184        )"
185    };
186
187    if sqlx::query(create_table_sql).execute(&pool).await.is_err() {
188        return;
189    }
190
191    let session_file = ".rustbasic_session";
192    let mut session_id = String::new();
193    let mut need_to_write = false;
194
195    if let Ok(content) = fs::read_to_string(session_file) {
196        session_id = content.trim().to_string();
197    }
198
199    let mut session_exists = false;
200
201    if !session_id.is_empty()
202        && let Ok(Some(_)) = sqlx::query("SELECT id FROM sessions WHERE id = ?")
203            .bind(&session_id)
204            .fetch_optional(&pool)
205            .await
206    {
207        session_exists = true;
208    }
209
210    if !session_exists {
211        let mut bytes = [0u8; 32];
212        rand::rng().fill_bytes(&mut bytes);
213        session_id = general_purpose::STANDARD.encode(bytes);
214        need_to_write = true;
215
216        let now = chrono::Utc::now().timestamp();
217        if sqlx::query("INSERT INTO sessions (id, payload, last_activity, ip_address) VALUES (?, ?, ?, ?)")
218            .bind(&session_id)
219            .bind("{}")
220            .bind(now)
221            .bind("127.0.0.1")
222            .execute(&pool)
223            .await
224            .is_ok()
225        {
226            println!("โœจ {} ({})", "Session baru berhasil dibuat dan disimpan di database.".green().bold(), session_id.cyan());
227        }
228    } else {
229        let now = chrono::Utc::now().timestamp();
230        let _ = sqlx::query("UPDATE sessions SET last_activity = ? WHERE id = ?")
231            .bind(now)
232            .bind(&session_id)
233            .execute(&pool)
234            .await;
235    }
236
237    if need_to_write {
238        let _ = fs::write(session_file, &session_id);
239    }
240}