diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2025-07-21 20:37:16 +0300 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2025-07-21 20:37:16 +0300 |
| commit | 30f50e5b31294abd75c4b629970ad4865108738d (patch) | |
| tree | 60521071763769c8a3ad13952808cfc1d0454bcf /src | |
| parent | 0ef072f64456f9e6a0105bd123987d66434a2254 (diff) | |
feat: add cli option to create user
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli.rs | 96 | ||||
| -rw-r--r-- | src/main.rs | 182 |
2 files changed, 235 insertions, 43 deletions
diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..e6cb7b4 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,96 @@ +use clap::{Parser, Subcommand}; +use std::env; +use sqlx::SqlitePool; +use uuid::Uuid; + +use silmataivas::users::{UserRepository, UserRole}; +use silmataivas::notifications::{NtfySettingsInput, NtfySettingsRepository}; +use silmataivas::weather_thresholds::WeatherThresholdRepository; +use tracing::{info, warn, error, debug, trace}; +use tracing_subscriber::{EnvFilter, FmtSubscriber}; + +#[derive(Parser)] +#[command(name = "silmataivas")] +struct Cli { + /// Increase output verbosity (-v, -vv, -vvv) + #[arg(short, long, action = clap::ArgAction::Count, global = true)] + verbose: u8, + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Create a new user with optional UUID + CreateUser { + uuid: Option<String>, + }, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + // Set up logging based on verbosity + let filter = match cli.verbose { + 0 => "warn", + 1 => "info", + 2 => "debug", + _ => "trace", + }; + let subscriber = FmtSubscriber::builder() + .with_env_filter(EnvFilter::new(filter)) + .finish(); + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + let db_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + let pool = SqlitePool::connect(&db_url).await?; + + match cli.command { + Commands::CreateUser { uuid } => { + let repo = UserRepository { db: &pool }; + let user_id = uuid.unwrap_or_else(|| Uuid::new_v4().to_string()); + let user = repo.create_user(Some(user_id.clone()), Some(UserRole::User)).await?; + + // Set up default NTFY settings + let ntfy_repo = NtfySettingsRepository { db: &pool }; + ntfy_repo.create(NtfySettingsInput { + user_id: user.id, + enabled: true, + topic: user_id.clone(), + server_url: "https://ntfy.sh".to_string(), + priority: 3, + title_template: None, + message_template: None, + }).await?; + + // Set up default weather thresholds + let threshold_repo = WeatherThresholdRepository { db: &pool }; + threshold_repo.create_threshold( + user.id, + "temperature".to_string(), + 35.0, + ">".to_string(), + true, + Some("Default: temperature > 35°C".to_string()), + ).await?; + threshold_repo.create_threshold( + user.id, + "rain".to_string(), + 50.0, + ">".to_string(), + true, + Some("Default: rain > 50mm".to_string()), + ).await?; + threshold_repo.create_threshold( + user.id, + "wind_speed".to_string(), + 80.0, + ">".to_string(), + true, + Some("Default: wind speed > 80km/h".to_string()), + ).await?; + + info!("User {} created", user_id); + } + } + Ok(()) +}
\ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2e4dc75..4e89234 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,18 @@ use axum::http::StatusCode; use axum::response::IntoResponse; use axum::routing::{post, put}; use axum::{Router, routing::get}; +use clap::{Parser, Subcommand}; use serde_json::json; use sqlx::SqlitePool; +use std::env; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::fs; +use tracing::{error, info}; +use tracing_subscriber::{EnvFilter, FmtSubscriber}; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; +use uuid::Uuid; mod auth; mod health; @@ -894,55 +902,143 @@ pub fn app_with_state(pool: std::sync::Arc<SqlitePool>) -> Router { .with_state(pool) } -use std::env; -use std::net::SocketAddr; -use std::sync::Arc; -use tokio::fs; -use uuid::Uuid; +#[derive(Parser)] +#[command(name = "silmataivas")] +struct Cli { + /// Increase output verbosity (-v, -vv, -vvv) + #[arg(short, long, action = clap::ArgAction::Count, global = true)] + verbose: u8, + #[command(subcommand)] + command: Option<Commands>, +} + +#[derive(Subcommand)] +enum Commands { + /// Start the Silmataivas server (default) + Server, + /// Create a new user with optional UUID + CreateUser { uuid: Option<String> }, +} #[tokio::main] -async fn main() { - // Set up database path - let db_path = - env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://./data/silmataivas.db".to_string()); - // Ensure data directory exists - let _ = fs::create_dir_all("./data").await; - // Connect to SQLite - let pool = SqlitePool::connect(&db_path) - .await - .expect("Failed to connect to DB"); - - // Create initial admin user if none exists - { - let repo = users::UserRepository { db: &pool }; - match repo.any_admin_exists().await { - Ok(false) => { - let admin_token = - env::var("ADMIN_TOKEN").unwrap_or_else(|_| Uuid::new_v4().to_string()); - match repo - .create_user(Some(admin_token.clone()), Some(users::UserRole::Admin)) - .await - { - Ok(_) => println!("Initial admin user created. Token: {admin_token}"), - Err(e) => eprintln!("Failed to create initial admin user: {e}"), +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + // Set up logging based on verbosity + let filter = match cli.verbose { + 0 => "warn", + 1 => "info", + 2 => "debug", + _ => "trace", + }; + let subscriber = FmtSubscriber::builder() + .with_env_filter(EnvFilter::new(filter)) + .finish(); + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + match cli.command.unwrap_or(Commands::Server) { + Commands::Server => { + // Set up database path + let db_path = env::var("DATABASE_URL") + .unwrap_or_else(|_| "sqlite://./data/silmataivas.db".to_string()); + // Ensure data directory exists + let _ = fs::create_dir_all("./data").await; + // Connect to SQLite + let pool = SqlitePool::connect(&db_path) + .await + .expect("Failed to connect to DB"); + + // Create initial admin user if none exists + { + let repo = users::UserRepository { db: &pool }; + match repo.any_admin_exists().await { + Ok(false) => { + let admin_token = + env::var("ADMIN_TOKEN").unwrap_or_else(|_| Uuid::new_v4().to_string()); + match repo + .create_user(Some(admin_token.clone()), Some(users::UserRole::Admin)) + .await + { + Ok(_) => info!("Initial admin user created. Token: {}", admin_token), + Err(e) => error!("Failed to create initial admin user: {}", e), + } + } + Ok(true) => { + // At least one admin exists, do nothing + } + Err(e) => { + error!("Failed to check for existing admin users: {}", e); + } } } - Ok(true) => { - // At least one admin exists, do nothing - } - Err(e) => { - eprintln!("Failed to check for existing admin users: {e}"); - } + + let app = app_with_state(Arc::new(pool)); + let addr = SocketAddr::from(([0, 0, 0, 0], 4000)); + let listener = tokio::net::TcpListener::bind(addr) + .await + .expect("Failed to bind address"); + info!("Listening on {}", listener.local_addr().unwrap()); + axum::serve(listener, app).await.unwrap(); } - } + Commands::CreateUser { uuid } => { + let db_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + let pool = SqlitePool::connect(&db_url).await?; + let repo = crate::users::UserRepository { db: &pool }; + let user_id = uuid.unwrap_or_else(|| Uuid::new_v4().to_string()); + let user = repo + .create_user(Some(user_id.clone()), Some(crate::users::UserRole::User)) + .await?; - let app = app_with_state(Arc::new(pool)); - let addr = SocketAddr::from(([0, 0, 0, 0], 4000)); - let listener = tokio::net::TcpListener::bind(addr) - .await - .expect("Failed to bind address"); - println!("Listening on {}", listener.local_addr().unwrap()); - axum::serve(listener, app).await.unwrap(); + // Set up default NTFY settings + let ntfy_repo = crate::notifications::NtfySettingsRepository { db: &pool }; + ntfy_repo + .create(crate::notifications::NtfySettingsInput { + user_id: user.id, + enabled: true, + topic: user_id.clone(), + server_url: "https://ntfy.sh".to_string(), + priority: 3, + title_template: None, + message_template: None, + }) + .await?; + + // Set up default weather thresholds + let threshold_repo = + crate::weather_thresholds::WeatherThresholdRepository { db: &pool }; + threshold_repo + .create_threshold( + user.id, + "temperature".to_string(), + 35.0, + ">".to_string(), + true, + Some("Default: temperature > 35°C".to_string()), + ) + .await?; + threshold_repo + .create_threshold( + user.id, + "rain".to_string(), + 50.0, + ">".to_string(), + true, + Some("Default: rain > 50mm".to_string()), + ) + .await?; + threshold_repo + .create_threshold( + user.id, + "wind_speed".to_string(), + 80.0, + ">".to_string(), + true, + Some("Default: wind speed > 80km/h".to_string()), + ) + .await?; + + info!("User {} created", user_id); + } + } + Ok(()) } #[cfg(test)] |
