Clearer code
- Comments - Better variable names
This commit is contained in:
parent
7b1cbcb0f0
commit
e12e57aaa7
41
src/db.rs
41
src/db.rs
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/main.rs
62
src/main.rs
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user