135 lines
4.5 KiB
Rust
135 lines
4.5 KiB
Rust
use anyhow::{Context, Result};
|
|
use sqlx::SqlitePool;
|
|
use std::{env, str::FromStr};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Config {
|
|
/// Telegram bot token (required)
|
|
pub telegram_token: String,
|
|
/// Database URL (required)
|
|
pub database_url: String,
|
|
/// Admin user ID for administrative commands
|
|
pub admin_user_id: Option<i64>,
|
|
/// Port for the web interface (future feature)
|
|
pub web_port: u16,
|
|
}
|
|
|
|
/// Database connection pool type alias for convenience
|
|
pub type DatabasePool = sqlx::SqlitePool;
|
|
|
|
impl Config {
|
|
/// Load and validate configuration from environment variables
|
|
///
|
|
/// This function expects a .env file to be present or environment variables to be set.
|
|
/// Required variables: TELOXIDE_TOKEN
|
|
/// Optional variables: DATABASE_URL, ADMIN_USER_ID, WEB_PORT
|
|
///
|
|
/// The configuration is automatically validated during construction.
|
|
pub fn from_env() -> Result<Self> {
|
|
dotenvy::dotenv()?;
|
|
env_logger::init();
|
|
|
|
let telegram_token = env::var("TELOXIDE_TOKEN")
|
|
.context("TELOXIDE_TOKEN environment variable is required")?;
|
|
|
|
let database_url =
|
|
env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite:pawctioneer_bot.db".to_string());
|
|
|
|
let admin_user_id = env::var("ADMIN_USER_ID")
|
|
.ok()
|
|
.and_then(|s| s.parse::<i64>().ok());
|
|
|
|
let web_port = env::var("WEB_PORT")
|
|
.unwrap_or_else(|_| "3000".to_string())
|
|
.parse::<u16>()
|
|
.context("WEB_PORT must be a valid port number")?;
|
|
|
|
let config = Config {
|
|
telegram_token,
|
|
database_url,
|
|
admin_user_id,
|
|
web_port,
|
|
};
|
|
|
|
// Automatically validate before returning
|
|
config.validate()?;
|
|
|
|
Ok(config)
|
|
}
|
|
|
|
/// Internal validation method called automatically by from_env()
|
|
///
|
|
/// This method validates the configuration and logs important settings.
|
|
/// It's called internally and doesn't need to be called manually.
|
|
fn validate(&self) -> Result<()> {
|
|
if self.telegram_token.is_empty() {
|
|
anyhow::bail!("Telegram token cannot be empty");
|
|
}
|
|
|
|
if self.database_url.is_empty() {
|
|
anyhow::bail!("Database URL cannot be empty");
|
|
}
|
|
|
|
// Log configuration (without sensitive data)
|
|
log::info!("Configuration loaded:");
|
|
log::info!(" Database URL: {}", self.database_url);
|
|
log::info!(" Web Port: {}", self.web_port);
|
|
|
|
if let Some(admin_id) = self.admin_user_id {
|
|
log::info!(" Admin User ID: {admin_id}");
|
|
} else {
|
|
log::info!(" Admin User ID: Not set");
|
|
}
|
|
|
|
log::info!(" Telegram Token: [CONFIGURED]");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Create a database connection pool using the configured database URL
|
|
///
|
|
/// This establishes a connection pool to the SQLite database and runs any pending migrations.
|
|
pub async fn create_database_pool(&self) -> Result<DatabasePool> {
|
|
log::info!("Connecting to database: {}", self.database_url);
|
|
|
|
// Create connection pool with sensible defaults for SQLite
|
|
// For SQLite, we need to ensure the database file can be created
|
|
let pool = SqlitePool::connect_with(
|
|
sqlx::sqlite::SqliteConnectOptions::from_str(&self.database_url)?
|
|
.create_if_missing(true)
|
|
.pragma("foreign_keys", "ON"), // Enable foreign key constraints
|
|
)
|
|
.await
|
|
.with_context(|| format!("Failed to connect to database: {}", self.database_url))?;
|
|
|
|
// Run database migrations
|
|
log::info!("Running database migrations...");
|
|
sqlx::migrate!("./migrations")
|
|
.run(&pool)
|
|
.await
|
|
.context("Failed to run database migrations")?;
|
|
log::info!("Database migrations completed successfully");
|
|
|
|
// Run health check
|
|
Self::health_check(&pool)
|
|
.await
|
|
.context("Database health check failed after connection")?;
|
|
|
|
log::info!("Database connection pool created successfully");
|
|
Ok(pool)
|
|
}
|
|
|
|
/// Perform a health check on the database connection
|
|
///
|
|
/// This verifies the database is accessible and responding to queries.
|
|
pub async fn health_check(pool: &DatabasePool) -> Result<()> {
|
|
sqlx::query("SELECT 1")
|
|
.execute(pool)
|
|
.await
|
|
.context("Database health check query failed")?;
|
|
|
|
log::debug!("Database health check passed");
|
|
Ok(())
|
|
}
|
|
}
|