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