rustbasic_cli/
database.rs1use 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 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 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 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}