1use std::fs::{self, OpenOptions};
2use std::io::{Read, Write};
3use chrono::Local;
4use colored::*;
5use super::utils::{to_snake_case, to_pascal_case};
6
7pub fn make_controller(name: &str) {
8 let pascal_name = to_pascal_case(name).replace("Controller", "");
9 let snake_name = to_snake_case(&pascal_name);
10 let class_name = format!("{}Controller", pascal_name);
11 let file_name = format!("{}_controller.rs", snake_name);
12 let file_path = format!("src/app/http/controllers/{}", file_name);
13
14 if std::path::Path::new(&file_path).exists() {
15 println!("{} {} {}", "⚠️ Controller".yellow(), file_path.cyan(), "sudah ada.".yellow());
16 return;
17 }
18
19 let template = format!(
20r#"/* ---------------------------------------------------------
21 * 📑 LABEL: {class_name} ({file_name})
22 * --------------------------------------------------------- */
23
24use crate::app::view;
25use rustbasic_core::requests::Request;
26use rustbasic_core::axum::response::IntoResponse;
27use rustbasic_core::minijinja::context;
28
29pub struct {class_name};
30
31impl {class_name} {{
32 pub async fn index(req: Request) -> impl IntoResponse {{
33 view(&req, "{snake_name}.rb.html", context! {{
34 title => "{class_name}"
35 }})
36 }}
37}}
38"#, class_name = class_name, file_name = file_name, snake_name = snake_name);
39
40 fs::write(&file_path, template).expect("Gagal membuat file controller");
41 println!("{} {}", "✅ Controller dibuat:".green(), file_path.cyan());
42
43 update_controller_mod_rs(&file_name.replace(".rs", ""));
44}
45
46pub fn update_controller_mod_rs(mod_name: &str) {
47 let mod_path = "src/app/http/controllers/mod.rs";
48 let mut content = String::new();
49 if let Ok(mut file) = fs::File::open(mod_path) {
50 file.read_to_string(&mut content).ok();
51 }
52
53 let line = format!("pub mod {};", mod_name);
54 if content.contains(&line) {
55 return;
56 }
57
58 let mut file = OpenOptions::new()
59 .append(true)
60 .open(mod_path)
61 .expect("Gagal membuka controllers/mod.rs");
62
63 writeln!(file, "{}", line).ok();
64 println!("{} {}", "📝".blue(), "controllers/mod.rs diperbarui.".dimmed());
65}
66
67pub fn make_middleware(name: &str) {
68 let snake_name = to_snake_case(name).replace("_middleware", "");
69 let fn_name = format!("{}_middleware", snake_name);
70 let file_name = format!("{}.rs", snake_name);
71 let file_path = format!("src/app/http/middleware/{}", file_name);
72
73 if std::path::Path::new(&file_path).exists() {
74 println!("{} {} {}", "⚠️ Middleware".yellow(), file_path.cyan(), "sudah ada.".yellow());
75 return;
76 }
77
78 let template = format!(
79r#"/* ---------------------------------------------------------
80 * 📑 LABEL: {label} (middleware/{file_name})
81 * --------------------------------------------------------- */
82
83use rustbasic_core::axum::{{
84 extract::Request,
85 middleware::Next,
86 response::Response,
87}};
88
89pub async fn {fn_name}(
90 req: Request,
91 next: Next,
92) -> Response {{
93 // Lakukan sesuatu sebelum request sampai ke controller
94
95 let response = next.run(req).await;
96
97 // Lakukan sesuatu setelah request selesai diproses
98
99 response
100}}
101"#, label = name.to_uppercase(), file_name = file_name, fn_name = fn_name);
102
103 fs::write(&file_path, template).expect("Gagal membuat file middleware");
104 println!("{} {}", "✅ Middleware dibuat:".green(), file_path.cyan());
105
106 update_middleware_mod_rs(&snake_name);
107}
108
109pub fn update_middleware_mod_rs(mod_name: &str) {
110 let mod_path = "src/app/http/middleware/mod.rs";
111 let mut content = String::new();
112 if let Ok(mut file) = fs::File::open(mod_path) {
113 file.read_to_string(&mut content).ok();
114 }
115
116 let line = format!("pub mod {};", mod_name);
117 if content.contains(&line) {
118 return;
119 }
120
121 let mut file = OpenOptions::new()
122 .append(true)
123 .open(mod_path)
124 .expect("Gagal membuka middleware/mod.rs");
125
126 writeln!(file, "{}", line).ok();
127 println!("{} {}", "📝".blue(), "middleware/mod.rs diperbarui.".dimmed());
128}
129
130pub fn make_model(name: &str) {
131 let snake_name = to_snake_case(name);
132 let table_name = format!("{}s", snake_name);
133 let file_path = format!("src/app/models/{}.rs", snake_name);
134
135 if std::path::Path::new(&file_path).exists() {
136 println!("{} {} {}", "⚠️ Model".yellow(), file_path.cyan(), "sudah ada.".yellow());
137 return;
138 }
139
140 let template = format!(
141r#"use rustbasic_core::sea_orm::entity::prelude::*;
142use serde::{{Deserialize, Serialize}};
143
144#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
145#[sea_orm(table_name = "{}")]
146pub struct Model {{
147 #[sea_orm(primary_key)]
148 pub id: i32,
149 pub created_at: Option<DateTime>,
150 pub updated_at: Option<DateTime>,
151}}
152
153#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
154pub enum Relation {{}}
155
156impl ActiveModelBehavior for ActiveModel {{}}
157"#, table_name);
158
159 fs::write(&file_path, template).expect("Gagal membuat file model");
160 println!("{} {}", "✅ Model dibuat:".green(), file_path.cyan());
161
162 update_mod_rs(&to_pascal_case(name), &snake_name);
163}
164
165pub fn update_mod_rs(class_name: &str, snake_name: &str) {
166 let mod_path = "src/app/models/mod.rs";
167 let mut content = String::new();
168 if let Ok(mut file) = fs::File::open(mod_path) {
169 file.read_to_string(&mut content).ok();
170 }
171
172 let mod_line = format!("pub mod {};", snake_name);
173 if content.contains(&mod_line) {
174 return;
175 }
176
177 let mut file = OpenOptions::new()
178 .append(true)
179 .open(mod_path)
180 .expect("Gagal membuka models/mod.rs");
181
182 writeln!(file, "{}", mod_line).ok();
183 writeln!(file, "pub use {}::Entity as {};", snake_name, class_name).ok();
184
185 println!("{} {}", "📝".blue(), "models/mod.rs diperbarui.".dimmed());
186}
187
188pub fn make_rust_migration(name: &str) {
189 let snake_name = to_snake_case(name);
190 let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
191 let mod_name = format!("m{}_{}", timestamp, snake_name);
192 let file_path = format!("database/migrations/{}.rs", mod_name);
193
194 if std::path::Path::new(&file_path).exists() {
195 println!("{} {} {}", "⚠️ Migration".yellow(), file_path.cyan(), "sudah ada.".yellow());
196 return;
197 }
198
199 let pascal_name = to_pascal_case(name);
200 let table_iden = format!("{}s", pascal_name);
201
202 let template = format!(
203r#"use sea_orm_migration::prelude::*;
204use async_trait::async_trait;
205
206#[derive(Iden)]
207enum {table_iden} {{
208 Table,
209 Id,
210 CreatedAt,
211 UpdatedAt,
212}}
213
214#[derive(Iden)]
215pub struct Migration;
216
217impl MigrationName for Migration {{
218 fn name(&self) -> &str {{
219 "{mod_name}"
220 }}
221}}
222
223#[async_trait]
224impl MigrationTrait for Migration {{
225 async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {{
226 manager
227 .create_table(
228 Table::create()
229 .table({table_iden}::Table)
230 .if_not_exists()
231 .col(
232 ColumnDef::new({table_iden}::Id)
233 .integer()
234 .not_null()
235 .auto_increment()
236 .primary_key(),
237 )
238 .col(
239 ColumnDef::new({table_iden}::CreatedAt)
240 .date_time()
241 .default(Expr::current_timestamp()),
242 )
243 .col(
244 ColumnDef::new({table_iden}::UpdatedAt)
245 .date_time()
246 .default(Expr::current_timestamp()),
247 )
248 .to_owned(),
249 )
250 .await
251 }}
252
253 async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {{
254 manager
255 .drop_table(Table::drop().table({table_iden}::Table).to_owned())
256 .await
257 }}
258}}
259"#, table_iden = table_iden, mod_name = mod_name);
260
261 fs::write(&file_path, template).expect("Gagal membuat file migration");
262 println!("{} {}", "✅ Migration Rust dibuat:".green(), file_path.cyan());
263
264 update_migration_mod_rs(&mod_name);
265}
266
267pub fn update_migration_mod_rs(mod_name: &str) {
268 let mod_path = "database/migrations/mod.rs";
269 let mut content = String::new();
270 if let Ok(mut file) = fs::File::open(mod_path) {
271 file.read_to_string(&mut content).ok();
272 }
273
274 if !content.contains(&format!("pub mod {};", mod_name)) {
276 if !content.ends_with('\n') {
277 content.push('\n');
278 }
279 content.push_str(&format!("pub mod {};\n", mod_name));
280 }
281
282 let search_pattern = "fn migrations() -> Vec<Box<dyn MigrationTrait>> {";
284 if let Some(_pos) = content.find(search_pattern) {
285 let insert_pos = content.find(" ]").unwrap_or(content.len());
286 content.insert_str(insert_pos, &format!(" Box::new({}::Migration),\n", mod_name));
287 }
288
289 fs::write(mod_path, content).expect("Gagal memperbarui database/migrations/mod.rs");
290 println!("{} {}", "📝".blue(), "database/migrations/mod.rs diperbarui.".dimmed());
291}
292
293pub fn make_seeder(name: &str) {
294 let pascal_name = to_pascal_case(name).replace("Seeder", "");
295 let snake_name = to_snake_case(&pascal_name);
296 let class_name = format!("{}Seeder", pascal_name);
297 let file_name = format!("{}_seeder.rs", snake_name);
298 let file_path = format!("database/seeders/{}", file_name);
299
300 if std::path::Path::new(&file_path).exists() {
301 println!("{} {} {}", "⚠️ Seeder".yellow(), file_path.cyan(), "sudah ada.".yellow());
302 return;
303 }
304
305 let template = format!(
306r#"#[allow(unused_imports)]
307use rustbasic_core::sea_orm::{{DatabaseConnection, Set, ActiveModelTrait}};
308use rustbasic_core::colored::Colorize;
309use rustbasic_core::seeder::SeederTrait;
310// use crate::app::models::{snake_name}; // Sesuaikan dengan model Anda
311
312pub struct {class_name};
313
314#[async_trait::async_trait]
315impl SeederTrait for {class_name} {{
316 async fn run(&self, _db: &DatabaseConnection) -> Result<(), rustbasic_core::sea_orm::DbErr> {{
317 println!(" {{}} Sedang memproses {class_name}...", "⏳".blue());
318
319 // Contoh:
320 /*
321 let _ = {snake_name}::ActiveModel {{
322 name: Set("Example Data".to_owned()),
323 ..Default::default()
324 }}.insert(_db).await?;
325 */
326
327 Ok(())
328 }}
329}}
330"#, class_name = class_name, snake_name = snake_name);
331
332 fs::write(&file_path, template).expect("Gagal membuat file seeder");
333 println!("{} {}", "✅ Seeder dibuat:".green(), file_path.cyan());
334
335 update_seeder_mod_rs(&class_name, &file_name.replace(".rs", ""));
336}
337
338pub fn update_seeder_mod_rs(class_name: &str, mod_name: &str) {
339 let db_mod_path = "database/seeders/mod.rs";
341 let mut db_content = fs::read_to_string(db_mod_path).expect("Gagal membaca seeders/mod.rs");
342 let mod_line = format!("pub mod {};", mod_name);
343 if !db_content.contains(&mod_line) {
344 db_content.push_str(&format!("{}\n", mod_line));
345 fs::write(db_mod_path, db_content).ok();
346 }
347
348 let config_path = "src/app/seeder.rs";
350 let mut config_content = fs::read_to_string(config_path).expect("Gagal membaca src/app/seeder.rs");
351 let search_pattern = "let seeders: Vec<Box<dyn SeederTrait>> = vec![";
352 if let Some(pos) = config_content.find(search_pattern) {
353 let insert_pos = pos + search_pattern.len();
354 config_content.insert_str(insert_pos, &format!("\n Box::new(seeders::{}::{}),", mod_name, class_name));
355 fs::write(config_path, config_content).ok();
356 }
357
358 println!("{} {}", "📝".blue(), "Pengaturan seeder diperbarui.".dimmed());
359}