My personal site cherry.computer
htmx tailwind axum askama
at main 106 lines 3.3 kB view raw
1mod scrapers; 2mod templates; 3 4use std::{env, net::SocketAddr, sync::Arc}; 5 6use crate::scrapers::{MediaType, apple_music::AppleMusicClient}; 7#[cfg(debug_assertions)] 8use crate::templates::am_auth_flow::AuthFlowTemplate; 9use crate::templates::{ 10 index::{IndexOptions, RootTemplate, Shas}, 11 media::{MediaTemplate, fetch_media_of_type}, 12}; 13 14use askama::Template; 15use axum::{ 16 Router, 17 extract::{Path, Query, State}, 18 http::{HeaderName, HeaderValue, StatusCode}, 19 response::{Html, IntoResponse}, 20 routing::get, 21}; 22use tower::ServiceBuilder; 23use tower_http::{ 24 compression::CompressionLayer, set_header::SetResponseHeaderLayer, trace::TraceLayer, 25}; 26 27#[derive(Clone)] 28struct AppState { 29 apple_music_client: Arc<AppleMusicClient>, 30 shas: Shas, 31} 32 33#[tokio::main] 34async fn main() -> anyhow::Result<()> { 35 tracing_subscriber::fmt::init(); 36 37 let apple_music_client = Arc::new(AppleMusicClient::new()?); 38 let shas = Shas { 39 website: env::var("MYIVO_GIT_SHA").ok(), 40 }; 41 let state = AppState { 42 apple_music_client, 43 shas, 44 }; 45 46 let app = Router::new() 47 .route("/", get(render_index_handler)) 48 .route("/media/{media_type}", get(render_media_partial_handler)); 49 #[cfg(debug_assertions)] 50 let app = app.route("/dev/am-auth-flow", get(render_apple_music_auth_flow)); 51 let app = app.with_state(state).layer( 52 ServiceBuilder::new() 53 .layer(TraceLayer::new_for_http()) 54 .layer(CompressionLayer::new()) 55 .layer(SetResponseHeaderLayer::overriding( 56 HeaderName::from_static("strict-transport-security"), 57 HeaderValue::from_static("max-age=2592000; includeSubDomains"), 58 )), 59 ); 60 61 let addr = SocketAddr::from(([0, 0, 0, 0], 53465)); 62 tracing::debug!("starting server on {addr}"); 63 let listener = tokio::net::TcpListener::bind(addr).await?; 64 axum::serve(listener, app).await?; 65 66 Ok(()) 67} 68 69async fn render_index_handler( 70 Query(options): Query<IndexOptions>, 71 State(state): State<AppState>, 72) -> impl IntoResponse { 73 let template = RootTemplate::new(state.apple_music_client, state.shas, &options); 74 template.render().map(Html).map_err(|err| { 75 tracing::error!("failed to render index: {err:?}"); 76 StatusCode::INTERNAL_SERVER_ERROR 77 }) 78} 79 80async fn render_media_partial_handler( 81 Path(media_type): Path<MediaType>, 82 State(state): State<AppState>, 83) -> impl IntoResponse { 84 let media = fetch_media_of_type(media_type, state.apple_music_client) 85 .await 86 .unwrap(); 87 let template = MediaTemplate { media_type, media }; 88 template.render().map(Html).map_err(|err| { 89 tracing::error!("failed to render {media_type} media: {err:?}"); 90 StatusCode::INTERNAL_SERVER_ERROR 91 }) 92} 93 94#[cfg(debug_assertions)] 95async fn render_apple_music_auth_flow( 96 #[allow(unused_variables)] State(state): State<AppState>, 97) -> impl IntoResponse { 98 let template = AuthFlowTemplate::new(&state.apple_music_client); 99 template 100 .and_then(|template| Ok(template.render()?)) 101 .map(Html) 102 .map_err(|err| { 103 tracing::error!("failed to render Apple Music auth flow: {err:?}"); 104 StatusCode::INTERNAL_SERVER_ERROR 105 }) 106}