feat: add sqlx transaction extractor to router

This commit is contained in:
Alexander Navarro 2025-02-22 18:54:44 -03:00
parent cfaf7ecc1f
commit a662b94b21
8 changed files with 79 additions and 14 deletions

View file

@ -16,6 +16,9 @@ migrate: (docker-compose "run dbmate migrate")
rollback: (docker-compose "run dbmate rollback") rollback: (docker-compose "run dbmate rollback")
build-frontend-watch:
watchexec --restart --watch frontend just build-frontend
build-frontend: build-frontend:
#!/usr/bin/env bun #!/usr/bin/env bun
import sassPlugin from "@alecodes/bun-plugin-sass"; import sassPlugin from "@alecodes/bun-plugin-sass";

16
Cargo.lock generated
View file

@ -69,6 +69,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"axum-macros",
"bytes", "bytes",
"form_urlencoded", "form_urlencoded",
"futures-util", "futures-util",
@ -116,6 +117,17 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "axum-sqlx-tx" name = "axum-sqlx-tx"
version = "0.10.0" version = "0.10.0"
@ -226,7 +238,10 @@ checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
@ -236,6 +251,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"axum-sqlx-tx", "axum-sqlx-tx",
"chrono",
"minijinja", "minijinja",
"minijinja-embed", "minijinja-embed",
"notify", "notify",

View file

@ -4,8 +4,9 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
axum = "0.8.1" axum = { version = "0.8.1", features = ["macros"] }
axum-sqlx-tx = "0.10.0" axum-sqlx-tx = "0.10.0"
chrono = { version = "0.4.39", features = ["serde"] }
minijinja = { version = "2.7.0", features = ["loader"] } minijinja = { version = "2.7.0", features = ["loader"] }
minijinja-embed = "2.7.0" minijinja-embed = "2.7.0"
notify = "8.0.0" notify = "8.0.0"

View file

@ -3,5 +3,5 @@ plugins = ["bun-plugin-sass"]
[install.scopes] [install.scopes]
"@alecodes" = { token = "$NPM_REGISTRY_TOKEN", url = "https://git.alecodes.page/api/packages/alecodes/npm/" } "@alecodes" = { url = "https://git.alecodes.page/api/packages/alecodes/npm/" }
"@mini-strap" = { token = "$NPM_REGISTRY_TOKEN", url = "https://git.alecodes.page/api/packages/alecodes/npm/" } "@mini-strap" = { url = "https://git.alecodes.page/api/packages/alecodes/npm/" }

View file

@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block content %}
<table>
<thead>
<tr>
<th class="msp-text-center">Database</th>
<th class="msp-text-center">Version</th>
<th class="msp-text-center">Database Name</th>
<th class="msp-text-center">Current User</th>
<th class="msp-text-center">Current Timestamp</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
<th>{{ row.database }}</th>
<th>{{ row.version }}</th>
<th>{{ row.current_database }}</th>
<th>{{ row.current_user }}</th>
<th>{{ row.current_timestamp }}</th>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}

View file

@ -1,12 +1,13 @@
mod error; mod error;
pub mod router; pub mod router;
use axum::extract::FromRef;
pub use error::{Error, Result, ResultTemplate}; pub use error::{Error, Result, ResultTemplate};
use minijinja::{Environment, Template}; use minijinja::{Environment, Template};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc;
pub type Tx = axum_sqlx_tx::Tx<sqlx::Postgres>; pub type Tx = axum_sqlx_tx::Tx<sqlx::Postgres>;
pub type TxState = axum_sqlx_tx::State<sqlx::Postgres>;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Link { pub struct Link {
@ -15,13 +16,15 @@ pub struct Link {
pub subpages: Vec<Self>, pub subpages: Vec<Self>,
} }
#[derive(Clone, FromRef)]
pub struct AppState { pub struct AppState {
tmpl_env: Environment<'static>, tmpl_env: Environment<'static>,
tx: TxState,
} }
impl AppState { impl AppState {
pub fn new(tmpl_env: Environment<'static>) -> Arc<Self> { pub fn new(tmpl_env: Environment<'static>, tx: TxState) -> Self {
Arc::new(AppState { tmpl_env }) AppState { tmpl_env, tx }
} }
pub fn get_template(&self, name: &str) -> Result<Template> { pub fn get_template(&self, name: &str) -> Result<Template> {

View file

@ -77,9 +77,8 @@ async fn main() -> Result<()> {
let app = router::new() let app = router::new()
.layer(TraceLayer::new_for_http().on_request(())) .layer(TraceLayer::new_for_http().on_request(()))
.with_state(AppState::new(tmpl_env))
.layer(tx_layer) .layer(tx_layer)
.with_state(tx_state); .with_state(AppState::new(tmpl_env, tx_state));
// Add hot reload only on dev mode // Add hot reload only on dev mode
#[cfg(debug_assertions)] #[cfg(debug_assertions)]

View file

@ -1,20 +1,37 @@
use axum::{extract::State, response::Html, routing::get, Router}; use axum::{extract::State, response::Html, routing::get, Router};
use chrono::Utc;
use minijinja::context; use minijinja::context;
use std::sync::Arc; use serde::Serialize;
use sqlx::prelude::FromRow;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use crate::{AppState, ResultTemplate}; use crate::{AppState, ResultTemplate, Tx};
pub fn new() -> Router<Arc<AppState>> { pub fn new() -> Router<AppState> {
Router::new() Router::new()
.route("/", get(handler_home)) .route("/", get(handler_home))
.nest_service("/assets", ServeDir::new("dist/assets")) .nest_service("/assets", ServeDir::new("dist/assets"))
} }
async fn handler_home(State(state): State<Arc<AppState>>) -> ResultTemplate { #[derive(FromRow, Debug, Serialize)]
let template = state.tmpl_env.get_template("base.html")?; struct ExampleRow {
pub database: String,
pub version: String,
pub current_database: String,
pub current_user: String,
pub current_timestamp: chrono::DateTime<Utc>,
}
let content = template.render(context!())?; async fn handler_home(State(state): State<AppState>, mut tx: Tx) -> ResultTemplate {
let template = state.tmpl_env.get_template("index.html")?;
let rows = sqlx::query_as::<_, ExampleRow>("select 'Postgres' as database, setting as version, current_database(), current_user, current_timestamp from pg_settings where name = 'server_version'")
.fetch_all(&mut tx)
.await?;
println!("{:?}", rows);
let content = template.render(context!(rows => rows))?;
Ok(Html(content)) Ok(Html(content))
} }