My personal site cherry.computer
htmx tailwind axum askama

feat: render scrobble partial into initial HTML returned (SSR)

This means the scrobble data will show on page load rather than waiting
for a JS fetch.

cherry.computer f470661c d640ae17

verified
+27 -10
+2 -3
Dockerfile
··· 13 13 WORKDIR /usr/src/myivo-server 14 14 COPY server . 15 15 16 + # point to minimised, production versions of build artefacts 17 + RUN sed -i "s|build/app|build/app.min|g" files/index.html 16 18 RUN cargo install --profile release --locked --path . 17 19 18 20 # run on different image ··· 24 26 25 27 WORKDIR /root 26 28 27 - COPY --from=build-js /usr/src/myivo/index.html ./ 28 - # point to minimised, production versions of build artefacts 29 - RUN sed -i "s|build/app|build/app.min|g" index.html 30 29 COPY --from=build-js /usr/src/myivo/images ./images 31 30 COPY --from=build-js /usr/src/myivo/fonts ./fonts 32 31 COPY --from=build-js /usr/src/myivo/build ./build
+1 -1
frontend/esbuild.js
··· 43 43 headers: req.headers, 44 44 }; 45 45 const route = 46 - req.url === "/scrobbles" 46 + req.url === "/" || req.url === "/scrobbles" 47 47 ? { hostname: "127.0.0.1", port: 8080 } 48 48 : { hostname: host, port }; 49 49 const routedOptions = { ...options, ...route };
+3 -5
frontend/index.html server/files/index.html
··· 86 86 /> 87 87 </div> 88 88 </div> 89 - <div 90 - class="scrobble-bar" 91 - hx-get="/scrobbles" 92 - hx-trigger="load, every 30s" 93 - /> 89 + <div class="scrobble-bar" hx-get="/scrobbles" hx-trigger="every 30s"> 90 + {{scrobbles}} 91 + </div> 94 92 </body> 95 93 </html>
+8
server/src/index.rs
··· 1 + use crate::scrobble_monitor::ScrobbleMonitor; 2 + 3 + pub async fn render_index(monitor: &mut ScrobbleMonitor) -> anyhow::Result<String> { 4 + let index_template = include_str!("../files/index.html"); 5 + let scrobble_partial = monitor.get_scrobble().await?; 6 + 7 + Ok(index_template.replace("{{scrobbles}}", &scrobble_partial.into_string())) 8 + }
+13 -1
server/src/main.rs
··· 1 + mod index; 1 2 mod scrobble; 2 3 mod scrobble_monitor; 3 4 4 5 use std::{env, net::SocketAddr}; 5 6 7 + use crate::index::render_index; 6 8 use crate::scrobble_monitor::ScrobbleMonitor; 7 9 8 10 use axum::{ 9 11 http::{HeaderName, HeaderValue, StatusCode}, 10 - response::IntoResponse, 12 + response::{Html, IntoResponse}, 11 13 routing::{get, get_service}, 12 14 Extension, Router, 13 15 }; ··· 24 26 let monitor = ScrobbleMonitor::new(env::var("LAST_FM_API_KEY")?); 25 27 26 28 let app = Router::new() 29 + .route("/", get(render_index_handler)) 27 30 .route("/scrobbles", get(get_scrobble)) 28 31 .fallback(get_service(ServeDir::new("."))) 29 32 .layer( ··· 43 46 axum::serve(listener, app).await?; 44 47 45 48 Ok(()) 49 + } 50 + 51 + async fn render_index_handler( 52 + Extension(mut monitor): Extension<ScrobbleMonitor>, 53 + ) -> impl IntoResponse { 54 + render_index(&mut monitor).await.map(Html).map_err(|err| { 55 + tracing::error!("failed to get data from last.fm: {err:?}"); 56 + StatusCode::BAD_GATEWAY 57 + }) 46 58 } 47 59 48 60 async fn get_scrobble(Extension(mut monitor): Extension<ScrobbleMonitor>) -> impl IntoResponse {