seed.rs
241 lignes · 7534 octets
//! Seed data binary for RustWarden //! //! This binary loads test data (Happy Days characters) into the database. //! It is intended for development and testing only. //! //! Usage: //! cargo run --bin seed //! cargo run --bin seed -- --force # Force execution even if data exists //! //! Safety: //! - Refuses to run in production environment //! - Checks for existing data before creating //! - Idempotent (safe to run multiple times) use chrono::Utc; use dotenvy::dotenv; use rustwarden_core::config::database::create_connection; use rustwarden_core::error::AppError; use rustwarden_core::services::password_service::PasswordService; use rustwarden_core::services::role_service::RoleService; use rustwarden_core::services::user_service::UserService; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set}; use std::env; use tracing::{error, info, warn}; /// Happy Days characters to seed const SEED_USERS: &[(&str, &str, &str)] = &[ ("richie", "richie@rustwarden.local", "user"), ("fonzie", "fonzie@rustwarden.local", "user"), ("potsie", "potsie@rustwarden.local", "user"), ("ralph", "ralph@rustwarden.local", "user"), ("joanie", "joanie@rustwarden.local", "admin"), ]; const HAPPY_DAYS_PASSWORD: &str = "happydays123"; fn validate_email_format(email: &str) -> Result<(), String> { if email.is_empty() { return Err("Email cannot be empty".to_string()); } let parts: Vec<&str> = email.split('@').collect(); if parts.len() != 2 { return Err(format!( "Email must contain exactly one @ symbol: {}", email )); } let local = parts[0]; let domain = parts[1]; if local.is_empty() { return Err(format!("Email local part cannot be empty: {}", email)); } if domain.is_empty() { return Err(format!("Email domain cannot be empty: {}", email)); } if !domain.contains('.') { return Err(format!( "Email domain must contain at least one dot: {}", email )); } Ok(()) } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { dotenv().ok(); tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); info!("Starting seed data loading..."); let environment = env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()); if environment == "production" { error!("Seed data cannot be loaded in production environment"); error!("Set ENVIRONMENT=development to allow seed data loading"); std::process::exit(1); } let force = env::args().any(|arg| arg == "--force"); let db = create_connection().await?; info!("Database connection established"); info!("Initializing default roles..."); match RoleService::initialize_default_roles(&db).await { Ok((admin_role, user_role, _app_manager_role)) => { info!( "Default roles initialized: admin={}, user={}", admin_role.id, user_role.id ); } Err(e) => { error!("Failed to initialize default roles: {}", e); return Err(format!("Failed to initialize default roles: {}", e).into()); } } use rustwarden_core::models::user; let user_count = user::Entity::find() .count(&db) .await .map_err(|e| AppError::Internal(format!("Database error: {}", e)))?; if user_count > 10 && !force { warn!("Database already contains {} users", user_count); warn!("Use --force to load seed data anyway"); warn!("Seed data loading skipped"); return Ok(()); } if user_count > 10 && force { warn!("Force mode: loading seed data despite existing users"); } let mut created = 0; let mut skipped = 0; for (username, email, role_name) in SEED_USERS { if let Err(e) = validate_email_format(email) { error!("{} - Skipping user creation", e); skipped += 1; continue; } let existing_user = user::Entity::find() .filter(user::Column::Username.eq(*username)) .one(&db) .await .map_err(AppError::Database)?; if existing_user.is_some() { info!("User '{}' already exists, skipping", username); skipped += 1; continue; } let password_hash = PasswordService::hash_password(HAPPY_DAYS_PASSWORD).await?; let user = UserService::create_user( &db, username.to_string(), email.to_string(), password_hash, false, ) .await?; // Mark seed user as email verified let mut user_model: rustwarden_core::models::user::ActiveModel = user.clone().into(); user_model.email_verified = Set(true); user_model.email_verified_at = Set(Some(Utc::now())); user_model.update(&db).await?; info!( "Created user: {} ({}) [email pre-verified]", username, email ); let role = RoleService::get_role_by_name(&db, role_name).await?; if let Some(role) = role { UserService::assign_role_to_user(&db, user.id, role.id).await?; info!(" Assigned role: {}", role_name); } else { warn!("Role '{}' not found, user created without role", role_name); } created += 1; } println!(); println!("Seed data loading complete!"); println!(); println!("Summary:"); println!(" Created: {} users", created); println!(" Skipped: {} users", skipped); println!(); if created > 0 { println!("Happy Days users created (email pre-verified):"); for (username, _, _) in SEED_USERS { println!(" - {} | Password: {}", username, HAPPY_DAYS_PASSWORD); } println!(); } println!("Login with any Happy Days user:"); println!(" Username: richie, fonzie, potsie, ralph, joanie"); println!(" Password: {}", HAPPY_DAYS_PASSWORD); println!(); Ok(()) } #[cfg(test)] mod tests { use super::validate_email_format; #[test] fn test_validate_email_format_valid() { assert!(validate_email_format("user@example.com").is_ok()); assert!(validate_email_format("richie@rustwarden.local").is_ok()); assert!(validate_email_format("test.user@domain.co.uk").is_ok()); assert!(validate_email_format("a@b.c").is_ok()); } #[test] fn test_validate_email_format_invalid() { assert!(validate_email_format("").is_err()); assert!(validate_email_format("no-at-symbol").is_err()); assert!(validate_email_format("@domain.com").is_err()); assert!(validate_email_format("user@").is_err()); assert!(validate_email_format("user@domain").is_err()); assert!(validate_email_format("user@@domain.com").is_err()); } #[test] fn test_seed_users_email_domain() { use super::SEED_USERS; for (_, email, _) in SEED_USERS { assert!( email.ends_with("@rustwarden.local"), "Email {} does not use @rustwarden.local domain", email ); assert!( validate_email_format(email).is_ok(), "Email {} has invalid format", email ); } } }
GitRust