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