Skip to main content

rustbasic_cli/
database.rs

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