Skip to main content

rustbasic_core/cli/
auth.rs

1use std::fs;
2use colored::*;
3use regex::Regex;
4use super::scaffolding::update_controller_mod_rs;
5
6pub async fn make_auth() {
7    println!("\n{}", "🔐 Scaffolding Authentication...".magenta().bold());
8
9    // 1. Create src/routes/auth.rs
10    let auth_route_path = "src/routes/auth.rs";
11    let auth_route_template = r#"use axum::{Router, routing::{get, post}, middleware::from_fn};
12use crate::app::http::controllers::auth;
13use crate::app::http::middleware::auth::guest_middleware;
14use crate::server::AppState;
15
16pub fn router() -> Router<AppState> {
17    Router::new()
18        .route("/login", get(auth::auth_controller::AuthController::login_page))
19        .route("/login", post(auth::auth_controller::AuthController::login))
20        .route("/register", get(auth::auth_controller::AuthController::register_page))
21        .route("/register", post(auth::auth_controller::AuthController::register))
22        .route("/forgot-password", get(auth::auth_controller::AuthController::forgot_password_page))
23        .route("/forgot-password", post(auth::auth_controller::AuthController::send_reset_link))
24        .route("/reset-password", get(auth::auth_controller::AuthController::reset_password_page))
25        .route("/reset-password", post(auth::auth_controller::AuthController::update_password))
26        .layer(from_fn(guest_middleware))
27}
28"#;
29    if !std::path::Path::new(auth_route_path).exists() {
30        fs::write(auth_route_path, auth_route_template).ok();
31        println!("   {} {}", "✅ Created:".green(), auth_route_path.cyan());
32    } else {
33        println!("   {} {}", "⚠️  Exists:".yellow(), auth_route_path.cyan());
34    }
35
36    // 2. Update src/routes/mod.rs
37    let routes_mod_path = "src/routes/mod.rs";
38    if let Ok(mut content) = fs::read_to_string(routes_mod_path) {
39        if !content.contains("pub mod auth;") {
40            content.push_str("pub mod auth;\n");
41            fs::write(routes_mod_path, content).ok();
42            println!("   {} {}", "📝 Updated:".blue(), routes_mod_path.cyan());
43        }
44    }
45
46    // 3. Update src/routes/web.rs
47    let web_route_path = "src/routes/web.rs";
48    if let Ok(mut content) = fs::read_to_string(web_route_path) {
49        if !content.contains("use crate::routes::auth as auth_routes;") {
50            content = content.replace("use axum::{Router, routing::get};", "use axum::{Router, routing::{get, post}, middleware::from_fn};");
51            content = content.replace("use crate::server::AppState;", "use crate::app::http::controllers::{auth, dashboard_controller};\nuse crate::app::http::middleware::auth::auth_middleware;\nuse crate::server::AppState;\nuse crate::routes::auth as auth_routes;");
52
53            let merge_logic = r#"let auth_protected_routes = Router::new()
54        .route("/dashboard", get(dashboard_controller::DashboardController::index))
55        .route("/logout", post(auth::auth_controller::AuthController::logout))
56        .layer(from_fn(auth_middleware));
57
58    Router::new()
59        .route("/", get(welcome_controller::index))
60        .route("/dev", get(welcome_controller::dev_info))
61        .merge(auth_routes::router())
62        .merge(auth_protected_routes)"#;
63
64            // Use regex for more robust replacement (includes leading spaces)
65            let re = Regex::new(r#"(?s)Router::new\(\s*\n\s*\.route\("/", get\(welcome_controller::index\)\)\s*\n\s*\.route\("/dev", get\(welcome_controller::dev_info\)\)"#).unwrap();
66            if re.is_match(&content) {
67                content = re.replace(&content, merge_logic).to_string();
68            } else {
69                // Fallback for simple replacement
70                content = content.replace("Router::new()\n        .route(\"/\", get(welcome_controller::index))\n        .route(\"/dev\", get(welcome_controller::dev_info))", merge_logic);
71            }
72            
73            fs::write(web_route_path, content).ok();
74            println!("   {} {}", "📝 Updated:".blue(), web_route_path.cyan());
75        }
76    }
77
78    // 3.1 Create Password Resets Migration
79    let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
80    let migration_name = format!("m{}_create_password_resets_table", timestamp);
81    let migration_path = format!("database/migrations/{}.rs", migration_name);
82    
83    // Check if any password reset migration already exists
84    let mut exists = false;
85    if let Ok(entries) = std::fs::read_dir("database/migrations") {
86        for entry in entries.flatten() {
87            if let Some(name) = entry.file_name().to_str() {
88                if name.ends_with("_create_password_resets_table.rs") {
89                    exists = true;
90                    println!("   {} {}", "⚠️  Exists:".yellow(), name.cyan());
91                    break;
92                }
93            }
94        }
95    }
96
97    if !exists {
98        let migration_template = format!(r#"use sea_orm_migration::prelude::*;
99
100#[derive(Iden)]
101enum PasswordResets {{
102    Table,
103    Email,
104    Token,
105    CreatedAt,
106}}
107
108#[derive(DeriveMigrationName)]
109pub struct Migration;
110
111#[async_trait::async_trait]
112impl MigrationTrait for Migration {{
113    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {{
114        manager
115            .create_table(
116                Table::create()
117                    .table(PasswordResets::Table)
118                    .if_not_exists()
119                    .col(ColumnDef::new(PasswordResets::Email).string().not_null().primary_key())
120                    .col(ColumnDef::new(PasswordResets::Token).string().not_null())
121                    .col(
122                        ColumnDef::new(PasswordResets::CreatedAt)
123                            .timestamp()
124                            .default(Expr::current_timestamp())
125                            .not_null(),
126                    )
127                    .to_owned(),
128            )
129            .await
130    }}
131
132    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {{
133        manager
134            .drop_table(Table::drop().table(PasswordResets::Table).to_owned())
135            .await
136    }}
137}}
138"#);
139        fs::write(&migration_path, migration_template).ok();
140        
141        super::scaffolding::update_migration_mod_rs(&migration_name);
142        println!("   {} {}", "✅ Created:".green(), format!("Migration {}", migration_name).cyan());
143    }
144
145    // 4. Create Controller Folder & mod.rs
146    let auth_controller_dir = "src/app/http/controllers/auth";
147    fs::create_dir_all(auth_controller_dir).ok();
148    let auth_controller_mod = "src/app/http/controllers/auth/mod.rs";
149    if !std::path::Path::new(auth_controller_mod).exists() {
150        fs::write(auth_controller_mod, "pub mod auth_controller;").ok();
151    }
152    update_controller_mod_rs("auth");
153
154    // 4.1 Create Password Resets Model
155    let model_path = "src/app/models/password_resets.rs";
156    if !std::path::Path::new(model_path).exists() {
157        let model_template = r#"use sea_orm::entity::prelude::*;
158use serde::{Deserialize, Serialize};
159
160#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
161#[sea_orm(table_name = "password_resets")]
162pub struct Model {
163    #[sea_orm(primary_key, auto_increment = false)]
164    pub email: String,
165    pub token: String,
166    pub created_at: DateTime,
167}
168
169#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
170pub enum Relation {}
171
172impl ActiveModelBehavior for ActiveModel {}
173"#;
174        fs::write(model_path, model_template).ok();
175        
176        // Update src/app/models/mod.rs
177        let models_mod_path = "src/app/models/mod.rs";
178        if let Ok(mut content) = fs::read_to_string(models_mod_path) {
179            if !content.contains("pub mod password_resets;") {
180                content.push_str("pub mod password_resets;\n");
181                fs::write(models_mod_path, content).ok();
182            }
183        }
184        println!("   {} {}", "✅ Created:".green(), "Model password_resets".cyan());
185    }
186
187    let auth_controller_path = "src/app/http/controllers/auth/auth_controller.rs";
188    if !std::path::Path::new(auth_controller_path).exists() {
189        let controller_template = r#"/* ---------------------------------------------------------
190 * 📑 LABEL: AUTH CONTROLLER (auth/auth_controller.rs)
191 * Menangani pendaftaran, login, dan logout user.
192 * --------------------------------------------------------- */
193
194use crate::app::view;
195use crate::app::models::users;
196use crate::requests::Request;
197use crate::responses::ResponseHelper;
198use crate::server::AppState;
199use axum::{response::IntoResponse, extract::State};
200use bcrypt::{hash, verify, DEFAULT_COST};
201use uuid::Uuid;
202use serde::Deserialize;
203use validator::Validate;
204use crate::mail::MailService;
205use minijinja::context;
206use sea_orm::{EntityTrait, ColumnTrait, QueryFilter, Set};
207
208#[derive(Deserialize, Validate)]
209pub struct RegisterRequest {
210    #[validate(length(min = 3, message = "Nama minimal 3 karakter"))]
211    pub name: String,
212    
213    #[validate(email(message = "Format email tidak valid"))]
214    pub email: String,
215    
216    #[validate(length(min = 8, message = "Password minimal 8 karakter"))]
217    pub password: String,
218}
219
220#[derive(Deserialize, Validate)]
221pub struct LoginRequest {
222    #[validate(email(message = "Format email tidak valid"))]
223    pub email: String,
224    pub password: String,
225    pub remember: Option<String>,
226}
227
228#[derive(Deserialize, Validate)]
229pub struct ForgotPasswordRequest {
230    #[validate(email(message = "Format email tidak valid"))]
231    pub email: String,
232}
233
234#[derive(Deserialize, Validate)]
235pub struct ResetPasswordRequest {
236    pub token: String,
237    #[validate(length(min = 8, message = "Password minimal 8 karakter"))]
238    pub password: String,
239}
240
241pub struct AuthController;
242
243impl AuthController {
244    /// Menampilkan halaman login
245    pub async fn login_page(req: Request) -> impl IntoResponse {
246        view(&req, "auth/login.rb.html", context! { title => "Login" })
247    }
248
249    /// Menampilkan halaman register
250    pub async fn register_page(req: Request) -> impl IntoResponse {
251        view(&req, "auth/register.rb.html", context! { title => "Daftar Akun" })
252    }
253
254    /// Proses Pendaftaran
255    pub async fn register(State(state): State<AppState>, req: Request) -> impl IntoResponse {
256        // 1. Validasi Input
257        let data = match req.validate::<RegisterRequest>() {
258            Ok(d) => d,
259            Err(_) => return ResponseHelper::redirect("/register"),
260        };
261
262        // 2. Cek apakah email sudah terdaftar
263        let existing = users::Entity::find()
264            .filter(users::Column::Email.eq(&data.email))
265            .one(&state.db)
266            .await
267            .ok()
268            .flatten();
269
270        if existing.is_some() {
271            return ResponseHelper::redirect_with_error("/register", "Email sudah terdaftar", req.session);
272        }
273
274        // 3. Hash Password
275        let hashed = hash(data.password, DEFAULT_COST).unwrap();
276
277        // 4. Simpan ke Database
278        let new_user = users::ActiveModel {
279            name: Set(data.name),
280            email: Set(data.email),
281            password: Set(hashed),
282            ..Default::default()
283        };
284
285        if let Err(e) = users::Entity::insert(new_user).exec(&state.db).await {
286            tracing::error!("Gagal menyimpan user: {}", e);
287            return ResponseHelper::redirect_with_error("/register", "Gagal mendaftar, coba lagi.", req.session);
288        }
289
290        ResponseHelper::redirect_with_success("/login", "Pendaftaran berhasil! Silakan login.", req.session)
291    }
292
293    /// Proses Login
294    pub async fn login(State(state): State<AppState>, req: Request) -> impl IntoResponse {
295        // 1. Validasi Input
296        let data = match req.validate::<LoginRequest>() {
297            Ok(d) => d,
298            Err(_) => return ResponseHelper::redirect("/login"),
299        };
300
301        // 2. Ambil User dari DB
302        let user = users::Entity::find()
303            .filter(users::Column::Email.eq(&data.email))
304            .one(&state.db)
305            .await
306            .ok()
307            .flatten();
308
309        if let Some(u) = user {
310            // 3. Verifikasi Password
311            if verify(data.password, &u.password).unwrap_or(false) {
312                // 4. Set Session
313                req.session.set("user_id", u.id);
314                
315                // Handle "Remember Me"
316                if data.remember.is_some() {
317                    // Set session expiration to 30 days if remember is checked
318                    // Note: implementation depends on axum_session configuration
319                    tracing::info!("Remember me checked for user: {}", u.email);
320                }
321
322                return ResponseHelper::redirect_with_success("/dashboard", "Selamat datang kembali!", req.session);
323            }
324        }
325
326        ResponseHelper::redirect_with_error("/login", "Email atau password salah", req.session)
327    }
328
329    /// Menampilkan halaman lupa password
330    pub async fn forgot_password_page(req: Request) -> impl IntoResponse {
331        view(&req, "auth/forgot.rb.html", context! { title => "Lupa Password" })
332    }
333
334    /// Kirim link reset password
335    pub async fn send_reset_link(State(state): State<AppState>, req: Request) -> impl IntoResponse {
336        let data = match req.validate::<ForgotPasswordRequest>() {
337            Ok(d) => d,
338            Err(_) => return ResponseHelper::redirect("/forgot-password"),
339        };
340
341        // 1. Cek apakah user ada
342        let user = users::Entity::find()
343            .filter(users::Column::Email.eq(&data.email))
344            .one(&state.db)
345            .await
346            .ok()
347            .flatten();
348
349        if let Some(u) = user {
350            // 2. Generate Token
351            let token = Uuid::new_v4().to_string();
352
353            // 3. Simpan Token
354            let reset = crate::app::models::password_resets::ActiveModel {
355                email: Set(u.email.clone()),
356                token: Set(token.clone()),
357                created_at: Set(chrono::Utc::now().naive_utc()),
358            };
359
360            let _ = crate::app::models::password_resets::Entity::insert(reset)
361                .on_conflict(
362                    sea_orm::sea_query::OnConflict::column(crate::app::models::password_resets::Column::Email)
363                        .update_column(crate::app::models::password_resets::Column::Token)
364                        .update_column(crate::app::models::password_resets::Column::CreatedAt)
365                        .to_owned()
366                )
367                .exec(&state.db)
368                .await;
369
370            // 4. Kirim Email (Gunakan Config::load().mail_*)
371            let config = crate::Config::load();
372            let app_name = std::env::var("APP_NAME").unwrap_or_else(|_| "RustBasic".to_string());
373            let reset_url = format!("{}/reset-password?token={}", config.app_url, token);
374
375            let subject = format!("Reset Password - {}", app_name);
376            let body = crate::view::render_to_string("emails/reset.rb.html", context! {
377                app_name => app_name,
378                reset_url => reset_url,
379            });
380
381            if let Err(e) = MailService::send_email(&u.email, &subject, &body).await {
382                tracing::error!("Gagal mengirim email reset: {}", e);
383            }
384
385            tracing::info!("Reset link for {}: {}", u.email, reset_url);
386        }
387
388        ResponseHelper::redirect_with_success("/login", "Jika email terdaftar, link reset password akan dikirim.", req.session)
389    }
390
391    /// Menampilkan halaman reset password
392    pub async fn reset_password_page(req: Request) -> impl IntoResponse {
393        let token = req.input_as_str("token").unwrap_or_default();
394        view(&req, "auth/reset.rb.html", context! { title => "Reset Password", token => token })
395    }
396
397    /// Proses update password baru
398    pub async fn update_password(State(state): State<AppState>, req: Request) -> impl IntoResponse {
399        let data = match req.validate::<ResetPasswordRequest>() {
400            Ok(d) => d,
401            Err(_) => return ResponseHelper::redirect("/login"),
402        };
403
404        // 1. Cari Token
405        let reset = crate::app::models::password_resets::Entity::find()
406            .filter(crate::app::models::password_resets::Column::Token.eq(&data.token))
407            .one(&state.db)
408            .await
409            .ok()
410            .flatten();
411
412        if let Some(r) = reset {
413            // 2. Cek Kadaluarsa (60 Menit)
414            let now = chrono::Utc::now().naive_utc();
415            let duration = now.signed_duration_since(r.created_at);
416            
417            if duration.num_minutes() > 60 {
418                // Hapus token yang sudah kadaluarsa
419                let _ = crate::app::models::password_resets::Entity::delete_by_id(r.email)
420                    .exec(&state.db)
421                    .await;
422                    
423                return ResponseHelper::redirect_with_error("/login", "Tautan reset password sudah kadaluarsa (melebihi 60 menit).", req.session);
424            }
425
426            // 3. Hash Password Baru
427            let hashed = bcrypt::hash(data.password, bcrypt::DEFAULT_COST).unwrap();
428
429            // 4. Update User
430            let _ = users::Entity::update_many()
431                .col_expr(users::Column::Password, sea_orm::sea_query::Expr::value(hashed))
432                .filter(users::Column::Email.eq(&r.email))
433                .exec(&state.db)
434                .await;
435
436            // 5. Hapus Token
437            let _ = crate::app::models::password_resets::Entity::delete_by_id(r.email)
438                .exec(&state.db)
439                .await;
440
441            return ResponseHelper::redirect_with_success("/login", "Password berhasil diubah. Silakan login.", req.session);
442        }
443
444        ResponseHelper::redirect_with_error("/login", "Token tidak valid atau sudah kadaluarsa.", req.session)
445    }
446
447    /// Proses Logout
448    pub async fn logout(req: Request) -> impl IntoResponse {
449        req.session.remove("user_id");
450        ResponseHelper::redirect_with_success("/", "Anda telah keluar.", req.session)
451    }
452}
453"#;
454        fs::write(auth_controller_path, controller_template).ok();
455        println!("   {} {}", "✅ Created:".green(), auth_controller_path.cyan());
456    }
457
458    // 5. Views
459    let auth_view_dir = "src/resources/views/auth";
460    fs::create_dir_all(auth_view_dir).ok();
461    
462    let login_template = r##"{% extends "layouts/app.rb.html" %}
463
464{% block title %}Login - RustBasic{% endblock %}
465
466{% block content %}
467<div class="split-screen">
468    <!-- Sisi Visual -->
469    <div class="split-side-visual">
470        <div class="visual-inner" style="max-width: 600px;">
471            <div style="margin-bottom: 2rem;">
472                <span class="badge" style="background: rgba(255,255,255,0.2); color: #fff; border: none;">RUSTBASIC FRAMEWORK</span>
473            </div>
474            <h1 style="font-size: 3.5rem; font-weight: 900; line-height: 1.1; margin-bottom: 1.5rem; text-shadow: 0 10px 20px rgba(0,0,0,0.1);">
475                Selamat Datang <br> <span style="color: rgba(255,255,255,0.8);">Kembali</span>
476            </h1>
477            <p style="font-size: 1.2rem; opacity: 0.9; margin-bottom: 2.5rem; font-weight: 500;">
478                Masuk untuk melanjutkan pengembangan aplikasi modern Anda dengan kecepatan dan keamanan Rust.
479            </p>
480            <div class="tech-stack" style="justify-content: center; margin-top: 1rem;">
481                <span class="badge">Axum</span>
482                <span class="badge">Sea-ORM</span>
483                <span class="badge">Minijinja</span>
484            </div>
485        </div>
486    </div>
487
488    <!-- Sisi Form -->
489    <div class="split-side-content">
490        <div class="content-container">
491            <div style="margin-bottom: 3rem;">
492                <h2 class="title" style="font-size: 2.8rem; margin-bottom: 0.5rem;">Login</h2>
493                <p class="text-muted" style="font-weight: 500;">Silakan masukkan akun Anda untuk melanjutkan.</p>
494            </div>
495
496            <form hx-post="/login" hx-target="body" hx-push-url="true" hx-indicator="#indicator" style="display: flex; flex-direction: column; gap: 1.5rem;">
497                <div>
498                    <label class="form-label">Email Address</label>
499                    <input type="email" name="email" class="form-control" placeholder="nama@email.com" value="{{ old.email }}" required autofocus>
500                    {% if errors.email %}
501                        <div style="color: var(--secondary); font-size: 0.85rem; margin-top: 0.5rem; font-weight: 600;">{{ errors.email }}</div>
502                    {% endif %}
503                </div>
504
505                <div>
506                    <label class="form-label">Password</label>
507                    <input type="password" name="password" class="form-control" placeholder="••••••••" required>
508                    {% if errors.password %}
509                        <div style="color: var(--secondary); font-size: 0.85rem; margin-top: 0.5rem; font-weight: 600;">{{ errors.password }}</div>
510                    {% endif %}
511                </div>
512
513                <div style="display: flex; justify-content: space-between; align-items: center;">
514                    <label style="display: flex; align-items: center; gap: 0.6rem; font-size: 0.9rem; cursor: pointer; color: var(--text-muted); font-weight: 500;">
515                        <input type="checkbox" name="remember" value="1" style="width: 18px; height: 18px; accent-color: var(--primary);"> 
516                        Ingat Saya
517                    </label>
518                    <a href="/forgot-password" style="font-size: 0.9rem; font-weight: 700; color: var(--primary); text-decoration: none;">Lupa Password?</a>
519                </div>
520
521                <div style="margin-top: 1rem;">
522                    <button type="submit" class="btn btn-primary w-100" style="padding: 1.25rem;">
523                        MASUK KE DASHBOARD
524                    </button>
525                </div>
526
527                <p class="text-center" style="font-size: 0.95rem; color: var(--text-muted); margin-top: 1rem;">
528                    Belum punya akun? <a href="/register" style="font-weight: 800; color: var(--accent); text-decoration: none;">Daftar Sekarang</a>
529                </p>
530            </form>
531        </div>
532    </div>
533</div>
534{% endblock %}
535"##;
536
537    let register_template = r##"{% extends "layouts/app.rb.html" %}
538
539{% block title %}Daftar - RustBasic{% endblock %}
540
541{% block content %}
542<div class="split-screen">
543    <!-- Sisi Visual -->
544    <div class="split-side-visual" style="background: linear-gradient(135deg, var(--secondary), var(--accent), var(--primary));">
545        <div class="visual-inner" style="max-width: 600px;">
546            <div style="margin-bottom: 2rem;">
547                <span class="badge" style="background: rgba(255,255,255,0.2); color: #fff; border: none;">JOIN REVOLUTION</span>
548            </div>
549            <h1 style="font-size: 3.5rem; font-weight: 900; line-height: 1.1; margin-bottom: 1.5rem; text-shadow: 0 10px 20px rgba(0,0,0,0.1);">
550                Mulai Perjalanan <br> <span style="color: rgba(255,255,255,0.8);">Anda</span>
551            </h1>
552            <p style="font-size: 1.2rem; opacity: 0.9; margin-bottom: 2.5rem; font-weight: 500;">
553                Bangun infrastruktur digital yang kokoh dengan framework yang mengutamakan keamanan dan performa maksimal.
554            </p>
555            <div style="display: flex; gap: 1rem; justify-content: center;">
556                <div style="text-align: center;">
557                    <div style="font-size: 1.5rem; font-weight: 800;">100%</div>
558                    <div style="font-size: 0.75rem; font-weight: 700; opacity: 0.8;">TYPE SAFE</div>
559                </div>
560                <div style="height: 40px; width: 1px; background: rgba(255,255,255,0.3);"></div>
561                <div style="text-align: center;">
562                    <div style="font-size: 1.5rem; font-weight: 800;">BLAZING</div>
563                    <div style="font-size: 0.75rem; font-weight: 700; opacity: 0.8;">FAST</div>
564                </div>
565            </div>
566        </div>
567    </div>
568
569    <!-- Sisi Form -->
570    <div class="split-side-content">
571        <div class="content-container">
572            <div style="margin-bottom: 3rem;">
573                <h2 class="title" style="font-size: 2.8rem; margin-bottom: 0.5rem;">Daftar</h2>
574                <p class="text-muted" style="font-weight: 500;">Lengkapi formulir di bawah untuk bergabung.</p>
575            </div>
576
577            <form hx-post="/register" hx-target="body" hx-push-url="true" hx-indicator="#indicator" style="display: flex; flex-direction: column; gap: 1.5rem;">
578                <div>
579                    <label class="form-label">Nama Lengkap</label>
580                    <input type="text" name="name" class="form-control" placeholder="Nama Anda" value="{{ old.name }}" required autofocus>
581                    {% if errors.name %}
582                        <div style="color: var(--secondary); font-size: 0.85rem; margin-top: 0.5rem; font-weight: 600;">{{ errors.name }}</div>
583                    {% endif %}
584                </div>
585
586                <div>
587                    <label class="form-label">Email Address</label>
588                    <input type="email" name="email" class="form-control" placeholder="nama@email.com" value="{{ old.email }}" required>
589                    {% if errors.email %}
590                        <div style="color: var(--secondary); font-size: 0.85rem; margin-top: 0.5rem; font-weight: 600;">{{ errors.email }}</div>
591                    {% endif %}
592                </div>
593
594                <div>
595                    <label class="form-label">Password</label>
596                    <input type="password" name="password" class="form-control" placeholder="Min. 8 karakter" required>
597                    {% if errors.password %}
598                        <div style="color: var(--secondary); font-size: 0.85rem; margin-top: 0.5rem; font-weight: 600;">{{ errors.password }}</div>
599                    {% endif %}
600                </div>
601
602                <div style="margin-top: 1rem;">
603                    <button type="submit" class="btn btn-primary w-100" style="padding: 1.25rem;">
604                        BUAT AKUN SEKARANG
605                    </button>
606                </div>
607
608                <p class="text-center" style="font-size: 0.95rem; color: var(--text-muted); margin-top: 1rem;">
609                    Sudah punya akun? <a href="/login" style="font-weight: 800; color: var(--accent); text-decoration: none;">Login Disini</a>
610                </p>
611            </form>
612        </div>
613    </div>
614</div>
615{% endblock %}
616"##;
617
618    let forgot_template = r##"{% extends "layouts/app.rb.html" %}
619
620{% block title %}Lupa Password - RustBasic{% endblock %}
621
622{% block content %}
623<div class="split-screen">
624    <!-- Sisi Visual -->
625    <div class="split-side-visual" style="background: linear-gradient(135deg, var(--primary), var(--secondary));">
626        <div class="visual-inner" style="max-width: 600px;">
627            <div style="margin-bottom: 2rem;">
628                <span class="badge" style="background: rgba(255,255,255,0.2); color: #fff; border: none;">SECURITY ASSIST</span>
629            </div>
630            <h1 style="font-size: 3.5rem; font-weight: 900; line-height: 1.1; margin-bottom: 1.5rem; text-shadow: 0 10px 20px rgba(0,0,0,0.1);">
631                Lupa <br> <span style="color: rgba(255,255,255,0.8);">Password?</span>
632            </h1>
633            <p style="font-size: 1.2rem; opacity: 0.9; margin-bottom: 2.5rem; font-weight: 500;">
634                Jangan khawatir, hal ini biasa terjadi. Kami akan membantu Anda mendapatkan akses kembali dengan aman.
635            </p>
636        </div>
637    </div>
638
639    <!-- Sisi Form -->
640    <div class="split-side-content">
641        <div class="content-container">
642            <div style="margin-bottom: 3rem;">
643                <h2 class="title" style="font-size: 2.8rem; margin-bottom: 0.5rem;">Reset</h2>
644                <p class="text-muted" style="font-weight: 500;">Masukkan email Anda untuk menerima link reset.</p>
645            </div>
646
647            <form hx-post="/forgot-password" hx-target="body" hx-push-url="true" hx-indicator="#indicator" style="display: flex; flex-direction: column; gap: 1.5rem;">
648                <div>
649                    <label class="form-label">Email Address</label>
650                    <input type="email" name="email" class="form-control" placeholder="nama@email.com" value="{{ old.email }}" required autofocus>
651                    {% if errors.email %}
652                        <div style="color: var(--secondary); font-size: 0.85rem; margin-top: 0.5rem; font-weight: 600;">{{ errors.email }}</div>
653                    {% endif %}
654                </div>
655
656                <div style="margin-top: 1rem;">
657                    <button type="submit" class="btn btn-primary w-100" style="padding: 1.25rem;">
658                        KIRIM LINK RESET PASSWORD
659                    </button>
660                </div>
661
662                <p class="text-center" style="font-size: 0.95rem; color: var(--text-muted); margin-top: 1rem;">
663                    Ingat password Anda? <a href="/login" style="font-weight: 800; color: var(--accent); text-decoration: none;">Login Disini</a>
664                </p>
665            </form>
666        </div>
667    </div>
668</div>
669{% endblock %}
670"##;
671
672    let login_view = "src/resources/views/auth/login.rb.html";
673    if !std::path::Path::new(login_view).exists() {
674        fs::write(login_view, login_template).ok();
675    }
676    
677    let register_view = "src/resources/views/auth/register.rb.html";
678    if !std::path::Path::new(register_view).exists() {
679        fs::write(register_view, register_template).ok();
680    }
681
682    let forgot_view = "src/resources/views/auth/forgot.rb.html";
683    if !std::path::Path::new(forgot_view).exists() {
684        fs::write(forgot_view, forgot_template).ok();
685    }
686
687    let reset_view = "src/resources/views/auth/reset.rb.html";
688    if !std::path::Path::new(reset_view).exists() {
689        let reset_template = r##"{% extends "layouts/app.rb.html" %}
690
691{% block title %}Reset Password - RustBasic{% endblock %}
692
693{% block content %}
694<div class="split-screen">
695    <!-- Sisi Visual -->
696    <div class="split-side-visual" style="background: linear-gradient(135deg, var(--accent), var(--primary));">
697        <div class="visual-inner" style="max-width: 600px;">
698            <div style="margin-bottom: 2rem;">
699                <span class="badge" style="background: rgba(255,255,255,0.2); color: #fff; border: none;">RECOVER ACCESS</span>
700            </div>
701            <h1 style="font-size: 3.5rem; font-weight: 900; line-height: 1.1; margin-bottom: 1.5rem; text-shadow: 0 10px 20px rgba(0,0,0,0.1);">
702                Buat Password <br> <span style="color: rgba(255,255,255,0.8);">Baru</span>
703            </h1>
704            <p style="font-size: 1.2rem; opacity: 0.9; margin-bottom: 2.5rem; font-weight: 500;">
705                Hampir selesai! Gunakan kombinasi password yang kuat untuk menjaga keamanan akun Anda di masa depan.
706            </p>
707        </div>
708    </div>
709
710    <!-- Sisi Form -->
711    <div class="split-side-content">
712        <div class="content-container">
713            <div style="margin-bottom: 3rem;">
714                <h2 class="title" style="font-size: 2.8rem; margin-bottom: 0.5rem;">Update</h2>
715                <p class="text-muted" style="font-weight: 500;">Silakan masukkan password baru Anda.</p>
716            </div>
717
718            <form hx-post="/reset-password" hx-target="body" hx-push-url="true" hx-indicator="#indicator" style="display: flex; flex-direction: column; gap: 1.5rem;">
719                <input type="hidden" name="token" value="{{ token }}">
720                
721                <div>
722                    <label class="form-label">Password Baru</label>
723                    <input type="password" name="password" class="form-control" placeholder="Min. 8 karakter" required autofocus>
724                    {% if errors.password %}
725                        <div style="color: var(--secondary); font-size: 0.85rem; margin-top: 0.5rem; font-weight: 600;">{{ errors.password }}</div>
726                    {% endif %}
727                </div>
728
729                <div style="margin-top: 1rem;">
730                    <button type="submit" class="btn btn-primary w-100" style="padding: 1.25rem;">
731                        SIMPAN PASSWORD BARU
732                    </button>
733                </div>
734            </form>
735        </div>
736    </div>
737</div>
738{% endblock %}
739"##;
740        fs::write(reset_view, reset_template).ok();
741    }
742
743    let email_reset_view = "src/resources/views/emails/reset.rb.html";
744    if !std::path::Path::new(email_reset_view).exists() {
745        fs::create_dir_all("src/resources/views/emails").ok();
746        let email_reset_template = r##"<!DOCTYPE html>
747<html>
748<head>
749    <meta charset="utf-8">
750    <style>
751        body { font-family: 'Inter', -apple-system, sans-serif; line-height: 1.6; color: #1a1a1a; margin: 0; padding: 0; }
752        .container { max-width: 600px; margin: 0 auto; padding: 40px 20px; }
753        .card { background: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 24px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
754        .header { background: linear-gradient(135deg, #6366f1, #a855f7); padding: 40px; text-align: center; color: white; }
755        .content { padding: 40px; }
756        .button { display: inline-block; padding: 14px 32px; background: #6366f1; color: #ffffff !important; text-decoration: none; border-radius: 8px; font-weight: 600; margin: 24px 0; }
757        .footer { padding: 24px; text-align: center; font-size: 13px; color: #6b7280; }
758        h1 { margin: 0; font-size: 24px; font-weight: 800; letter-spacing: -0.025em; }
759        p { margin: 16px 0; color: #4b5563; }
760        .divider { height: 1px; background: #f3f4f6; margin: 24px 0; }
761    </style>
762</head>
763<body>
764    <div class="container">
765        <div class="card">
766            <div class="header">
767                <h1>{{ app_name }}</h1>
768            </div>
769            <div class="content">
770                <h2 style="margin: 0; color: #111827; font-size: 20px;">Halo!</h2>
771                <p>Anda menerima email ini karena kami menerima permintaan reset password untuk akun Anda di <strong>{{ app_name }}</strong>.</p>
772                
773                <div style="text-align: center;">
774                    <a href="{{ reset_url }}" class="button">Reset Password Saya</a>
775                </div>
776
777                <p style="font-size: 14px; color: #9ca3af;">Link ini akan kadaluarsa dalam 60 menit. Jika Anda tidak merasa meminta reset password, abaikan saja email ini.</p>
778                
779                <div class="divider"></div>
780                
781                <p style="font-size: 12px; color: #9ca3af;">
782                    Jika Anda kesulitan menekan tombol, salin dan tempel URL berikut ke browser Anda:<br>
783                    <span style="word-break: break-all; color: #6366f1;">{{ reset_url }}</span>
784                </p>
785            </div>
786        </div>
787        <div class="footer">
788            &copy; 2026 {{ app_name }}. All rights reserved.
789        </div>
790    </div>
791</body>
792</html>
793"##;
794        fs::write(email_reset_view, email_reset_template).ok();
795    }
796
797    let dashboard_view = "src/resources/views/dashboard.rb.html";
798    if !std::path::Path::new(dashboard_view).exists() {
799        let dashboard_template = r##"{% extends "layouts/app.rb.html" %}
800
801{% block title %}{{ title }} - RustBasic{% endblock %}
802
803{% block content %}
804<div class="split-screen" style="background: #f8faff;">
805    <!-- Sidebar / Navigation (Kiri) -->
806    <div class="split-side-visual" style="flex: 0.35; align-items: flex-start; text-align: left; padding: 3rem; background: linear-gradient(180deg, var(--text-main), #2d3436);">
807        <div style="width: 100%;">
808            <div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 3rem;">
809                <div style="width: 50px; height: 50px; background: var(--primary); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-weight: 900; color: white; font-size: 1.5rem;">
810                    R
811                </div>
812                <h2 style="font-size: 1.5rem; font-weight: 800; color: white;">RustBasic</h2>
813            </div>
814
815            <div style="background: rgba(255,255,255,0.05); padding: 1.5rem; border-radius: 1.5rem; border: 1px solid rgba(255,255,255,0.1); margin-bottom: 3rem;">
816                <div style="display: flex; align-items: center; gap: 1rem;">
817                    <div style="width: 45px; height: 45px; background: var(--accent); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 800; color: white; font-size: 1.2rem;">
818                        {{ user_name[0] | upper }}
819                    </div>
820                    <div>
821                        <div style="font-weight: 700; color: white; font-size: 0.95rem;">{{ user_name }}</div>
822                        <div style="font-size: 0.8rem; color: rgba(255,255,255,0.5);">Administrator</div>
823                    </div>
824                </div>
825            </div>
826
827            <nav style="display: flex; flex-direction: column; gap: 0.5rem;">
828                <a href="/dashboard" class="btn" style="background: var(--primary); color: white; justify-content: flex-start; text-transform: none; letter-spacing: normal; padding: 1rem 1.5rem; border-radius: 12px;">
829                    📊 Dashboard Overview
830                </a>
831                <a href="/" class="btn" style="color: rgba(255,255,255,0.6); justify-content: flex-start; text-transform: none; letter-spacing: normal; padding: 1rem 1.5rem;">
832                    🏠 Main Website
833                </a>
834            </nav>
835
836            <div style="margin-top: 5rem;">
837                <form hx-post="/logout" hx-target="body" style="margin:0;">
838                    <button type="submit" class="btn w-100" style="background: rgba(239, 68, 68, 0.1); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.2); border-radius: 12px; font-weight: 700; padding: 1rem;">
839                        🚪 KELUAR SISTEM
840                    </button>
841                </form>
842            </div>
843        </div>
844    </div>
845
846    <!-- Main Workspace (Kanan) -->
847    <div class="split-side-content" style="flex: 1.2; align-items: flex-start; justify-content: flex-start; padding: 0;">
848        <div style="width: 100%; padding: 4rem;">
849            <header style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4rem;">
850                <div>
851                    <h1 class="title" style="font-size: 2.5rem; text-align: left; margin-bottom: 0.25rem;">Overview</h1>
852                    <p class="text-muted" style="font-weight: 500;">Selamat datang kembali, kendalikan project Anda.</p>
853                </div>
854                <div style="display: flex; gap: 1rem;">
855                    <div class="badge" style="background: white; padding: 0.8rem 1.5rem; box-shadow: 0 4px 6px rgba(0,0,0,0.02);">
856                        Server: <span style="color: var(--primary);">Running</span>
857                    </div>
858                </div>
859            </header>
860
861            <!-- Stats Grid -->
862            <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 2rem; margin-bottom: 4rem;">
863                <div style="background: white; border-radius: 24px; padding: 2rem; box-shadow: 0 10px 20px rgba(0,0,0,0.02); border: 1px solid rgba(0,0,0,0.03);">
864                    <div style="color: var(--text-muted); font-size: 0.85rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 1.5rem;">
865                        User Terdaftar
866                    </div>
867                    <div style="display: flex; align-items: baseline; gap: 0.5rem;">
868                        <div style="font-size: 3rem; font-weight: 900; color: var(--text-main);">{{ total_users }}</div>
869                        <div style="color: #10b981; font-weight: 700; font-size: 0.9rem;">↑ 12%</div>
870                    </div>
871                </div>
872
873                <div style="background: white; border-radius: 24px; padding: 2rem; box-shadow: 0 10px 20px rgba(0,0,0,0.02); border: 1px solid rgba(0,0,0,0.03);">
874                    <div style="color: var(--text-muted); font-size: 0.85rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 1.5rem;">
875                        Response Time
876                    </div>
877                    <div style="display: flex; align-items: baseline; gap: 0.5rem;">
878                        <div style="font-size: 3rem; font-weight: 900; color: var(--accent);">24</div>
879                        <div style="color: var(--accent); font-weight: 700; font-size: 0.9rem;">ms</div>
880                    </div>
881                </div>
882
883                <div style="background: white; border-radius: 24px; padding: 2rem; box-shadow: 0 10px 20px rgba(0,0,0,0.02); border: 1px solid rgba(0,0,0,0.03);">
884                    <div style="color: var(--text-muted); font-size: 0.85rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 1.5rem;">
885                        Database Status
886                    </div>
887                    <div style="display: flex; align-items: center; gap: 0.8rem; padding: 0.5rem 0;">
888                        <div style="width: 12px; height: 12px; background: #10b981; border-radius: 50%; box-shadow: 0 0 10px #10b981;"></div>
889                        <div style="font-size: 1.5rem; font-weight: 800; color: #10b981;">HEALTHY</div>
890                    </div>
891                </div>
892            </div>
893
894            <!-- Main Panel -->
895            <div class="glass-panel" style="max-width: none; padding: 3rem; margin: 0; border-radius: 32px; background: linear-gradient(135deg, white, #f1f3f5);">
896                <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 2rem;">
897                    <div>
898                        <h3 style="font-size: 1.8rem; font-weight: 800; margin-bottom: 0.5rem;">Informasi Server</h3>
899                        <p class="text-muted">Detail lingkungan eksekusi RustBasic Anda.</p>
900                    </div>
901                    <span class="badge" style="background: var(--primary); color: white;">v2026.1</span>
902                </div>
903                
904                <div style="background: var(--text-main); color: #00ff00; padding: 2rem; border-radius: 16px; font-family: monospace; font-size: 0.9rem; line-height: 1.6; box-shadow: inset 0 2px 10px rgba(0,0,0,0.5);">
905                    <div style="color: #636e72;">// RustBasic Kernel System</div>
906                    <div>[OK] Compiled with Axum 0.8.2</div>
907                    <div>[OK] Database Pool: Sea-ORM Connection Established</div>
908                    <div>[OK] Live Reload: Active on port 4000</div>
909                    <div>[OK] Workers: 8 logical threads spawned</div>
910                </div>
911            </div>
912        </div>
913    </div>
914</div>
915{% endblock %}
916"##;
917        fs::write(dashboard_view, dashboard_template).ok();
918    }
919    
920    // 6. Create Dashboard Controller
921    let dashboard_controller_path = "src/app/http/controllers/dashboard_controller.rs";
922    if !std::path::Path::new(dashboard_controller_path).exists() {
923        let dashboard_template = r#"use crate::app::view;
924use crate::app::models::users;
925use crate::requests::Request;
926use crate::server::AppState;
927use axum::{response::IntoResponse, extract::State};
928use minijinja::context;
929use sea_orm::{EntityTrait, PaginatorTrait};
930
931pub struct DashboardController;
932
933impl DashboardController {
934    pub async fn index(State(state): State<AppState>, req: Request) -> impl IntoResponse {
935        let user_id = req.session.get::<i32>("user_id").unwrap_or(0);
936        let user = users::Entity::find_by_id(user_id).one(&state.db).await.ok().flatten();
937        let total_users = users::Entity::find().count(&state.db).await.unwrap_or(0);
938
939        view(&req, "dashboard.rb.html", context! {
940            title => "Dashboard",
941            user_name => user.as_ref().map(|u| u.name.clone()).unwrap_or("Guest".to_string()),
942            user_email => user.as_ref().map(|u| u.email.clone()).unwrap_or_default(),
943            total_users => total_users,
944        })
945    }
946}
947"#;
948        fs::write(dashboard_controller_path, dashboard_template).ok();
949        println!("   {} {}", "✅ Created:".green(), dashboard_controller_path.cyan());
950    }
951    update_controller_mod_rs("dashboard_controller");
952
953    println!("   {} Folder src/resources/views/auth dan dashboard siap.", "✅ Views:".green());
954
955    // 6. Update welcome.rb.html
956    let welcome_path = "src/resources/views/welcome.rb.html";
957    if let Ok(content) = fs::read_to_string(welcome_path) {
958        if !content.contains("{% if auth %}") {
959            println!("   {} {}", "⚠️  Manual:".yellow(), "Pastikan welcome.rb.html memiliki tombol login/register.".dimmed());
960        } else {
961            println!("   {} {}", "✅ OK:".green(), "welcome.rb.html sudah memiliki logika auth.".dimmed());
962        }
963    }
964
965    println!("\n{}", "✨ Authentication scaffolded successfully!".green().bold());
966    println!("{}", "Jalankan 'cargo rustbasic route:list' untuk melihat route baru.".dimmed());
967}
968
969pub async fn remove_auth() {
970    println!("\n{}", "🗑️  Removing Authentication Scaffold...".red().bold());
971
972    // 1. Delete src/routes/auth.rs
973    let auth_route_path = "src/routes/auth.rs";
974    if std::path::Path::new(auth_route_path).exists() {
975        fs::remove_file(auth_route_path).ok();
976        println!("   {} {}", "✅ Deleted:".green(), auth_route_path.cyan());
977    }
978
979    // 2. Update src/routes/mod.rs
980    let routes_mod_path = "src/routes/mod.rs";
981    if let Ok(mut content) = fs::read_to_string(routes_mod_path) {
982        if content.contains("pub mod auth;") {
983            content = content.replace("pub mod auth;\n", "");
984            fs::write(routes_mod_path, content).ok();
985            println!("   {} {}", "📝 Updated:".blue(), routes_mod_path.cyan());
986        }
987    }
988
989    // 3. Update src/routes/web.rs
990    let web_route_path = "src/routes/web.rs";
991    if let Ok(mut content) = fs::read_to_string(web_route_path) {
992        let mut changed = false;
993        
994        // Remove imports
995        if content.contains("use axum::{Router, routing::{get, post}, middleware::from_fn};") {
996            content = content.replace("use axum::{Router, routing::{get, post}, middleware::from_fn};", "use axum::{Router, routing::get};");
997            changed = true;
998        }
999        
1000        let imports_to_remove = [
1001            "use crate::app::http::controllers::{auth, dashboard_controller};\n",
1002            "use crate::app::http::middleware::auth::auth_middleware;\n",
1003            "use crate::routes::auth as auth_routes;\n",
1004            "use crate::app::http::controllers::{auth, dashboard_controller};",
1005            "use crate::app::http::middleware::auth::auth_middleware;",
1006            "use crate::routes::auth as auth_routes;",
1007        ];
1008        
1009        for imp in imports_to_remove {
1010            if content.contains(imp) {
1011                content = content.replace(imp, "");
1012                changed = true;
1013            }
1014        }
1015
1016        // Remove auth_protected_routes logic and restore basic Router
1017        if content.contains("let auth_protected_routes = Router::new()") {
1018            let re = Regex::new(r##"(?s)\s*let auth_protected_routes = Router::new\(\).*?\.layer\(from_fn\(auth_middleware\)\);\s*"##).unwrap();
1019            content = re.replace(&content, "\n").to_string();
1020            
1021            content = content.replace(".merge(auth_routes::router())", "");
1022            content = content.replace(".merge(auth_protected_routes)", "");
1023            
1024            // Restore clean Router::new()
1025            let clean_router = r#"    Router::new()
1026        .route("/", get(welcome_controller::index))
1027        .route("/dev", get(welcome_controller::dev_info))"#;
1028            
1029            let router_re = Regex::new(r##"(?s)Router::new\(\).*?\.route\(\s*\"/dev\"\s*,\s*get\(welcome_controller::dev_info\)\s*\)"##).unwrap();
1030            content = router_re.replace(&content, clean_router).to_string();
1031            
1032            // Final cleanup of multiple newlines
1033            let multi_newline_re = Regex::new(r#"\n{3,}"#).unwrap();
1034            content = multi_newline_re.replace_all(&content, "\n\n").to_string();
1035            
1036            changed = true;
1037        }
1038
1039        if changed {
1040            fs::write(web_route_path, content).ok();
1041            println!("   {} {}", "📝 Updated:".blue(), web_route_path.cyan());
1042        }
1043    }
1044
1045    // 7. Delete Controllers
1046    let auth_controller_dir = "src/app/http/controllers/auth";
1047    if std::path::Path::new(auth_controller_dir).exists() {
1048        fs::remove_dir_all(auth_controller_dir).ok();
1049        println!("   {} {}", "✅ Deleted:".green(), auth_controller_dir.cyan());
1050    }
1051
1052    // 7.1 Delete Password Resets Migration & Model
1053    if let Ok(entries) = std::fs::read_dir("database/migrations") {
1054        for entry in entries.flatten() {
1055            if let Some(name) = entry.file_name().to_str() {
1056                if name.ends_with("_create_password_resets_table.rs") {
1057                    let path = entry.path();
1058                    fs::remove_file(&path).ok();
1059                    println!("   {} {}", "✅ Deleted:".green(), path.display().to_string().cyan());
1060                }
1061            }
1062        }
1063    }
1064    
1065    let model_path = "src/app/models/password_resets.rs";
1066    if std::path::Path::new(model_path).exists() {
1067        fs::remove_file(model_path).ok();
1068        println!("   {} {}", "✅ Deleted:".green(), model_path.cyan());
1069    }
1070
1071    // 8. Delete Views
1072    let auth_view_dir = "src/resources/views/auth";
1073    if std::path::Path::new(auth_view_dir).exists() {
1074        fs::remove_dir_all(auth_view_dir).ok();
1075        println!("   {} {}", "✅ Deleted:".green(), auth_view_dir.cyan());
1076    }
1077
1078    // 6. Delete Dashboard Controller
1079    let dashboard_path = "src/app/http/controllers/dashboard_controller.rs";
1080    if std::path::Path::new(dashboard_path).exists() {
1081        fs::remove_file(dashboard_path).ok();
1082        println!("   {} {}", "✅ Deleted:".green(), dashboard_path.cyan());
1083    }
1084
1085    // 7. Update src/app/http/controllers/mod.rs
1086    let controllers_mod_path = "src/app/http/controllers/mod.rs";
1087    if let Ok(mut content) = fs::read_to_string(controllers_mod_path) {
1088        let mut changed = false;
1089        if content.contains("pub mod auth;") {
1090            content = content.replace("pub mod auth;\n", "");
1091            changed = true;
1092        }
1093        if content.contains("pub mod dashboard_controller;") {
1094            content = content.replace("pub mod dashboard_controller;\n", "");
1095            changed = true;
1096        }
1097        if changed {
1098            fs::write(controllers_mod_path, content).ok();
1099            println!("   {} {}", "📝 Updated:".blue(), controllers_mod_path.cyan());
1100        }
1101    }
1102
1103    // 7.2 Update src/app/models/mod.rs
1104    let models_mod_path = "src/app/models/mod.rs";
1105    if let Ok(mut content) = fs::read_to_string(models_mod_path) {
1106        if content.contains("pub mod password_resets;") {
1107            content = content.replace("pub mod password_resets;\n", "");
1108            content = content.replace("pub mod password_resets;", "");
1109            fs::write(models_mod_path, content).ok();
1110            println!("   {} {}", "📝 Updated:".blue(), models_mod_path.cyan());
1111        }
1112    }
1113
1114    // 7.3 Update database/migrations/mod.rs
1115    let migration_mod_path = "database/migrations/mod.rs";
1116    if let Ok(content) = fs::read_to_string(migration_mod_path) {
1117        let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
1118        let mut changed = false;
1119        
1120        // Remove the mod line
1121        lines.retain(|line| {
1122            if line.contains("_create_password_resets_table;") {
1123                changed = true;
1124                false
1125            } else if line.contains("Box::new(") && line.contains("_create_password_resets_table::Migration") {
1126                changed = true;
1127                false
1128            } else {
1129                true
1130            }
1131        });
1132
1133        if changed {
1134            fs::write(migration_mod_path, lines.join("\n")).ok();
1135            println!("   {} {}", "📝 Updated:".blue(), migration_mod_path.cyan());
1136        }
1137    }
1138
1139    println!("\n{}", "✨ Authentication removed successfully!".green().bold());
1140}