Clearer code

- Comments
- Better variable names
This commit is contained in:
Candifloss 2026-04-23 16:37:08 +05:30
parent 7b1cbcb0f0
commit e12e57aaa7
3 changed files with 93 additions and 35 deletions

View File

@ -2,40 +2,45 @@ use rusqlite::types::Value;
use rusqlite::{Connection, Result}; use rusqlite::{Connection, Result};
use std::sync::Mutex; use std::sync::Mutex;
pub struct Db { /// Thin wrapper around a `SQLite` connection.
conn: Mutex<Connection>, /// Ensures safe access via Mutex (one query at a time).
pub struct SqliteDb {
connection: Mutex<Connection>,
} }
impl Db { impl SqliteDb {
pub fn open(path: &str) -> Result<Self> { /// Open a database file and create a shared DB handle.
pub fn open(db_path: &str) -> Result<Self> {
Ok(Self { Ok(Self {
conn: Mutex::new(Connection::open(path)?), connection: Mutex::new(Connection::open(db_path)?),
}) })
} }
pub fn get_table(&self, table: &str) -> Result<(Vec<String>, Vec<Vec<String>>)> { /// Fetch full table data (columns + rows as strings).
let conn = self.conn.lock().unwrap(); pub fn fetch_table(&self, table_name: &str) -> Result<(Vec<String>, Vec<Vec<String>>)> {
// Lock ensures only one request uses the connection at a time
let conn = self.connection.lock().unwrap();
// WARNING: table name is interpolated → trust only internal input // SQL query
let query = format!("SELECT * FROM {table}"); let query = format!("SELECT * FROM {table_name}");
let mut stmt = conn.prepare(&query)?; let mut stmt = conn.prepare(&query)?;
let columns = stmt let column_names = stmt
.column_names() .column_names()
.iter() .iter()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let col_count = stmt.column_count(); let column_count = stmt.column_count();
let rows_iter = stmt.query_map([], move |row| { let rows_iter = stmt.query_map([], move |row| {
let mut r = Vec::with_capacity(col_count); let mut row_values = Vec::with_capacity(column_count);
for i in 0..col_count { for i in 0..column_count {
let val: Value = row.get(i)?; let value: Value = row.get(i)?;
let s = match val { let cell = match value {
Value::Null => "<null>".to_string(), Value::Null => "<null>".to_string(),
Value::Integer(i) => i.to_string(), Value::Integer(i) => i.to_string(),
Value::Real(f) => f.to_string(), Value::Real(f) => f.to_string(),
@ -43,14 +48,14 @@ impl Db {
Value::Blob(b) => format!("<blob {} bytes>", b.len()), Value::Blob(b) => format!("<blob {} bytes>", b.len()),
}; };
r.push(s); row_values.push(cell);
} }
Ok(r) Ok(row_values)
})?; })?;
let rows = rows_iter.collect::<Result<Vec<_>>>()?; let rows = rows_iter.collect::<Result<Vec<_>>>()?;
Ok((columns, rows)) Ok((column_names, rows))
} }
} }

View File

@ -6,25 +6,67 @@ use tokio::net::TcpListener;
mod db; mod db;
mod routes; mod routes;
use db::Db; use db::SqliteDb;
#[derive(Parser)] /// Application-wide shared state.
struct Args { /// Everything handlers need should live here.
#[arg(long)] #[derive(Clone)]
db: String, pub struct AppState {
// Shared handle to the SQLite wrapper (not the raw connection itself)
pub db: Arc<SqliteDb>,
} }
#[tokio::main] /// CLI arguments
#[derive(Parser)]
struct Args {
/// Path to `SQLite` database file (`--dbpath <path>`)
#[arg(long)]
dbpath: String,
}
#[tokio::main] // Create Tokio runtime and run async main future
async fn main() { async fn main() {
// Parse CLI args into `Args`
let args = Args::parse(); let args = Args::parse();
let db = Arc::new(Db::open(&args.db).expect("db open failed")); // Create shared database handle (single SQLite connection wrapped for safe shared access)
// `Arc` allows multiple concurrent request handlers to share the same DB handle (shared ownership)
let db_handle = Arc::new(SqliteDb::open(&args.dbpath).expect("failed to open database"));
let app = Router::new().route("/", get(routes::index)).with_state(db); // Build shared app state (stored inside the router)
let app_state = AppState {
// SQLite connection
db: db_handle,
};
let listener = TcpListener::bind("127.0.0.1:8040").await.unwrap(); // Build router (maps incoming HTTP requests to handler functions)
let app = Router::new()
.route(
// Match path "/"
"/",
// Handler for GET request
get(
// On GET "/", Axum extracts handler inputs (e.g., `State<AppState>`) and then calls `routes::index`
routes::index,
),
)
// Store shared application state in the router; handlers can retrieve it using the `State` extractor
.with_state(app_state);
// Bind TCP socket to localhost:8040
let listener = TcpListener::bind("127.0.0.1:8040")
// Async because this is I/O
.await
.unwrap();
println!("http://127.0.0.1:8040"); println!("http://127.0.0.1:8040");
axum::serve(listener, app).await.unwrap(); // Start server - This is the core runtime loop.
// accept connections → route requests → run handlers → send responses
axum::serve(
listener, // Accept connections from listener
app, // Axum routes incoming requests through `app`, which dispatches to the matched handler
)
.await
.unwrap();
} }

View File

@ -1,10 +1,10 @@
use askama::Template; use askama::Template;
use axum::{extract::State, response::Html}; use axum::{extract::State, response::Html};
use std::sync::Arc;
use crate::db::Db; use crate::AppState;
/// Askama template for rendering a table page.
/// Field names must match variables used in `templates/table.html`.
#[derive(Template)] #[derive(Template)]
#[template(path = "table.html")] #[template(path = "table.html")]
pub struct TableTemplate { pub struct TableTemplate {
@ -13,15 +13,26 @@ pub struct TableTemplate {
pub rows: Vec<Vec<String>>, pub rows: Vec<Vec<String>>,
} }
pub async fn index(State(db): State<Arc<Db>>) -> Html<String> { /// HTTP handler for GET /
/// `State<AppState>` is extracted by Axum from the router.
/// This gives the handler access to shared application state.
pub async fn index(State(app_state): State<AppState>) -> Html<String> {
// `table_name` hardcoded for now; will become dynamic later
let table_name = "users"; let table_name = "users";
let (columns, rows) = db.get_table(table_name).unwrap();
let tmpl = TableTemplate { // Query DB: returns (column names, row data as strings)
let (columns, rows) = app_state // Access shared SQLite handle from AppState
.db // Shared SQLite wrapper handle
.fetch_table(table_name)
.unwrap(); // Fetch data from the table (column names, rows) as strings
// Prepare data for HTML template
let template = TableTemplate {
table_name: table_name.into(), table_name: table_name.into(),
columns, columns,
rows, rows,
}; };
Html(tmpl.render().unwrap()) // Render template → HTML string → wrap as HTTP response
Html(template.render().unwrap())
} }