simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit e0e3d681a922eb0b6d85130a144e150eae226f34
parent e09ad3174c4e02ee4d203556cf4b90fc4821acb6
Author: Silas Brack <silasbrack@gmail.com>
Date:   Tue,  9 Jun 2026 20:07:43 +0200

feat: add /api/* reverse proxy for local development

Proxy TrailBase API requests through the Axum app so SSE subscriptions
and other /api/* calls work locally without a separate reverse proxy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
MCargo.lock | 18+++++++++++++++++-
MCargo.toml | 2+-
Msrc/routes.rs | 38++++++++++++++++++++++++++++++++++++++
3 files changed, 56 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1663,6 +1663,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", + "futures-util", "h2", "http", "http-body", @@ -1684,12 +1685,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams 0.4.2", "web-sys", ] @@ -1730,7 +1733,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.5.0", "web-sys", ] @@ -2604,6 +2607,19 @@ dependencies = [ [[package]] name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-streams" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" diff --git a/Cargo.toml b/Cargo.toml @@ -13,6 +13,6 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "2" tracing = "0.1" -reqwest = { version = "0.12", features = ["json"] } +reqwest = { version = "0.12", features = ["json", "stream"] } trailbase-client = "0.8" url = "2" diff --git a/src/routes.rs b/src/routes.rs @@ -1,6 +1,7 @@ use axum::{ Router, body::Body, + extract::{Request, State}, http::{StatusCode, header}, response::{IntoResponse, Response}, routing::{get, post}, @@ -70,6 +71,7 @@ pub fn create_router(state: AppState) -> Router { .route("/story/{id}/edit", post(handlers::edit_story)) // Comments + .route("/story/{id}/comments", get(handlers::story_comments_fragment)) .route("/story/{id}/comment", post(handlers::create_comment)) .route("/comment/{id}/reply", get(handlers::show_reply_form)) .route("/comment/{id}/reply", post(handlers::submit_reply)) @@ -101,9 +103,45 @@ pub fn create_router(state: AppState) -> Router { .route("/resend-verification", get(handlers::show_resend_verification)) .route("/resend-verification", post(handlers::resend_verification)) + // Reverse proxy to TrailBase for /api/* (SSE subscriptions, etc.) + .route("/api/{*path}", axum::routing::any(proxy_to_trailbase)) + // Static files .route("/static/style.css", get(serve_style)) .route("/static/datastar.js", get(serve_datastar)) .route("/static/{*path}", get(static_404)) .with_state(state) } + +async fn proxy_to_trailbase( + State(state): State<AppState>, + request: Request, +) -> Response { + let path = request.uri().path_and_query() + .map(|pq| pq.as_str()) + .unwrap_or(request.uri().path()); + + let url = format!("{}{}", state.trailbase_url.trim_end_matches('/'), path); + + let response = match state.http_client.get(&url).send().await { + Ok(resp) => resp, + Err(_) => { + return Response::builder() + .status(StatusCode::BAD_GATEWAY) + .body(Body::from("TrailBase unavailable")) + .unwrap(); + } + }; + + let status = response.status(); + let mut builder = Response::builder().status(status); + + // Forward content-type and SSE-relevant headers + for (name, value) in response.headers() { + builder = builder.header(name, value); + } + + // Stream the body through for SSE support + let body = Body::from_stream(response.bytes_stream()); + builder.body(body).unwrap() +}