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