use std::{net::SocketAddr, sync::Arc};
use anyhow::Context;
use axum::Router;
use clap::Parser;
use sqlx::AnyPool;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use uuid::Uuid;
mod api;
mod assets;
mod config;
mod models;
use config::DatabaseConfig;
#[derive(Parser, Debug, Clone)]
#[command(name = "sql-web")]
#[command(about = "A web-based database browser for SQLite, MySQL, and PostgreSQL")]
pub struct Args {
#[arg(short, long)]
pub database_url: String,
#[arg(short = 'H', long, default_value = "127.0.0.1")]
pub host: String,
#[arg(short, long, default_value = "8080")]
pub port: u16,
#[arg(short, long)]
pub readonly: bool,
#[arg(short = 'R', long, default_value = "50")]
pub rows_per_page: usize,
#[arg(short = 'Q', long, default_value = "1000")]
pub query_rows_per_page: usize,
#[arg(long)]
pub debug: bool,
}
#[derive(Clone)]
pub struct AppState {
pub args: Args,
pub db_config: DatabaseConfig,
pub pool: AnyPool,
pub auth_token: String,
}
pub type SharedState = Arc<AppState>;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
init_tracing(args.debug);
sqlx::any::install_default_drivers();
let mut db_config = DatabaseConfig::from_url(&args.database_url)
.map_err(|error| anyhow::anyhow!("Invalid database URL: {error}"))?;
db_config.readonly = db_config.readonly || args.readonly;
let pool = AnyPool::connect(&db_config.url)
.await
.context("Failed to connect to database")?;
let addr: SocketAddr = format!("{}:{}", args.host, args.port)
.parse()
.context("Invalid bind address")?;
let state = Arc::new(AppState {
args,
db_config,
pool,
auth_token: Uuid::new_v4().to_string(),
});
let app = Router::new()
.nest("/api", api::router(state.clone()))
.fallback(assets::serve)
.with_state(state);
let listener = tokio::net::TcpListener::bind(addr)
.await
.with_context(|| format!("Failed to bind to {addr}"))?;
tracing::info!("sql-web listening on http://{addr}");
axum::serve(listener, app).await?;
Ok(())
}
fn init_tracing(debug: bool) {
let default_filter = if debug { "sql_web=debug,info" } else { "info" };
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| default_filter.into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
}