From e70170ee5b2f32447c0cbe5bc050a48ebc884fd5 Mon Sep 17 00:00:00 2001 From: rm-dr <96270320+rm-dr@users.noreply.github.com> Date: Sat, 8 Nov 2025 09:33:12 -0800 Subject: [PATCH] Merge asset and page server --- Cargo.lock | 16 +- Cargo.toml | 2 - crates/lib/assetserver/Cargo.toml | 8 - crates/lib/assetserver/src/lib.rs | 14 - crates/lib/page/Cargo.toml | 1 + crates/lib/page/src/servable/asset.rs | 44 +++ crates/lib/page/src/servable/mod.rs | 1 + crates/lib/page/src/servable/page.rs | 11 +- crates/lib/page/src/servable/redirect.rs | 5 +- crates/lib/page/src/server.rs | 52 ++- crates/macro/macro-assets/Cargo.toml | 15 - crates/macro/macro-assets/src/lib.rs | 309 ------------------ crates/service/service-webpage/Cargo.toml | 3 - .../service-webpage/src/pages/handouts.rs | 5 +- .../service-webpage/src/pages/index.rs | 6 +- .../service/service-webpage/src/pages/mod.rs | 22 +- .../service-webpage/src/routes/assets.rs | 206 ------------ .../service/service-webpage/src/routes/mod.rs | 180 +++++++++- 18 files changed, 279 insertions(+), 621 deletions(-) delete mode 100644 crates/lib/assetserver/Cargo.toml delete mode 100644 crates/lib/assetserver/src/lib.rs create mode 100644 crates/lib/page/src/servable/asset.rs delete mode 100644 crates/macro/macro-assets/Cargo.toml delete mode 100644 crates/macro/macro-assets/src/lib.rs delete mode 100644 crates/service/service-webpage/src/routes/assets.rs diff --git a/Cargo.lock b/Cargo.lock index d5d329a..ae1cf44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,10 +139,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f8ebf5827e4ac4fd5946560e6a99776ea73b596d80898f357007317a7141e47" -[[package]] -name = "assetserver" -version = "0.0.1" - [[package]] name = "ast_node" version = "5.0.0" @@ -1450,14 +1446,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "macro-assets" -version = "0.0.1" -dependencies = [ - "quote", - "syn 2.0.108", -] - [[package]] name = "macro-sass" version = "0.0.1" @@ -1772,6 +1760,7 @@ dependencies = [ "maud", "parking_lot", "serde", + "toolbox", "tower-http", "tracing", ] @@ -2504,13 +2493,11 @@ dependencies = [ name = "service-webpage" version = "0.0.1" dependencies = [ - "assetserver", "axum", "chrono", "emojis", "lazy_static", "libservice", - "macro-assets", "macro-sass", "markdown-it", "maud", @@ -2523,7 +2510,6 @@ dependencies = [ "tokio", "toml", "toolbox", - "tower-http", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index d203286..07457e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,9 +64,7 @@ type_complexity = "allow" # [workspace.dependencies] -macro-assets = { path = "crates/macro/macro-assets" } macro-sass = { path = "crates/macro/macro-sass" } -assetserver = { path = "crates/lib/assetserver" } libservice = { path = "crates/lib/libservice" } toolbox = { path = "crates/lib/toolbox" } page = { path = "crates/lib/page" } diff --git a/crates/lib/assetserver/Cargo.toml b/crates/lib/assetserver/Cargo.toml deleted file mode 100644 index ebcffd2..0000000 --- a/crates/lib/assetserver/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "assetserver" -version = { workspace = true } -rust-version = { workspace = true } -edition = { workspace = true } - -[lints] -workspace = true diff --git a/crates/lib/assetserver/src/lib.rs b/crates/lib/assetserver/src/lib.rs deleted file mode 100644 index 9fbfcb6..0000000 --- a/crates/lib/assetserver/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -/// A static asset with compile-time embedded data. -pub trait Asset { - /// The common URL prefix for all assets (e.g., "/assets") - const URL_PREFIX: &'static str; - - /// The specific URL path for this asset (e.g., "/logo.png") - const URL_POSTFIX: &'static str; - - /// The full URL for this asset (e.g., "/assets/logo.png") - const URL: &'static str; - - /// The embedded file contents as a byte slice - const BYTES: &'static [u8]; -} diff --git a/crates/lib/page/Cargo.toml b/crates/lib/page/Cargo.toml index c3df260..4c4af4a 100644 --- a/crates/lib/page/Cargo.toml +++ b/crates/lib/page/Cargo.toml @@ -8,6 +8,7 @@ edition = { workspace = true } workspace = true [dependencies] +toolbox = { workspace = true } libservice = { workspace = true } axum = { workspace = true } diff --git a/crates/lib/page/src/servable/asset.rs b/crates/lib/page/src/servable/asset.rs new file mode 100644 index 0000000..2799809 --- /dev/null +++ b/crates/lib/page/src/servable/asset.rs @@ -0,0 +1,44 @@ +use axum::http::{ + HeaderMap, HeaderValue, StatusCode, + header::{self}, +}; +use std::pin::Pin; +use toolbox::mime::MimeType; + +use crate::{Rendered, RenderedBody, RequestContext, Servable}; + +pub struct StaticAsset { + pub bytes: &'static [u8], + pub mime: MimeType, +} + +impl Servable for StaticAsset { + fn render<'a>( + &'a self, + _ctx: &'a RequestContext, + ) -> Pin + 'a + Send + Sync>> { + Box::pin(async { + let mut headers = HeaderMap::with_capacity(3); + + #[expect(clippy::unwrap_used)] + headers.insert( + header::CONTENT_TYPE, + HeaderValue::from_str(&self.mime.to_string()).unwrap(), + ); + + headers.insert( + header::CACHE_CONTROL, + HeaderValue::from_str(&format!("immutable, public, max-age={}", 60 * 60 * 24 * 30)) + .unwrap(), + ); + + return Rendered { + code: StatusCode::OK, + headers, + body: RenderedBody::Static(self.bytes), + ttl: None, + immutable: true, + }; + }) + } +} diff --git a/crates/lib/page/src/servable/mod.rs b/crates/lib/page/src/servable/mod.rs index 964841f..1713a49 100644 --- a/crates/lib/page/src/servable/mod.rs +++ b/crates/lib/page/src/servable/mod.rs @@ -1,2 +1,3 @@ +pub mod asset; pub mod page; pub mod redirect; diff --git a/crates/lib/page/src/servable/page.rs b/crates/lib/page/src/servable/page.rs index e8d2b5b..f7888ca 100644 --- a/crates/lib/page/src/servable/page.rs +++ b/crates/lib/page/src/servable/page.rs @@ -7,7 +7,7 @@ use maud::{Markup, Render, html}; use serde::Deserialize; use std::pin::Pin; -use crate::{Rendered, RequestContext, Servable}; +use crate::{Rendered, RenderedBody, RequestContext, Servable}; // // MARK: metadata @@ -67,6 +67,7 @@ impl Render for PageMetadata { // Some HTML pub struct Page { pub meta: PageMetadata, + pub immutable: bool, /// How long this page's html may be cached. /// This controls the maximum age of a page shown to the user. @@ -94,10 +95,11 @@ impl Default for Page { fn default() -> Self { Page { meta: Default::default(), - html_ttl: Some(TimeDelta::seconds(60 * 24 * 30)), - //css_ttl: Duration::from_secs(60 * 24 * 30), + html_ttl: Some(TimeDelta::seconds(60 * 60 * 24 * 30)), + //css_ttl: Duration::from_secs(60 * 60 * 24 * 30), //generate_css: None, generate_html: Box::new(|_, _| Box::pin(async { html!() })), + immutable: true, } } } @@ -125,8 +127,9 @@ impl Servable for Page { return Rendered { code: StatusCode::OK, headers, - body: html.0.into_bytes(), + body: RenderedBody::Markup(html), ttl: self.html_ttl, + immutable: self.immutable, }; }) } diff --git a/crates/lib/page/src/servable/redirect.rs b/crates/lib/page/src/servable/redirect.rs index 0eccf1e..b687596 100644 --- a/crates/lib/page/src/servable/redirect.rs +++ b/crates/lib/page/src/servable/redirect.rs @@ -5,7 +5,7 @@ use axum::http::{ header::{self, InvalidHeaderValue}, }; -use crate::{Rendered, RequestContext, Servable}; +use crate::{Rendered, RenderedBody, RequestContext, Servable}; pub struct Redirect { to: HeaderValue, @@ -31,8 +31,9 @@ impl Servable for Redirect { return Rendered { code: StatusCode::PERMANENT_REDIRECT, headers, - body: Vec::new(), + body: RenderedBody::Empty, ttl: None, + immutable: true, }; }) } diff --git a/crates/lib/page/src/server.rs b/crates/lib/page/src/server.rs index a3c716c..3fd661f 100644 --- a/crates/lib/page/src/server.rs +++ b/crates/lib/page/src/server.rs @@ -8,6 +8,7 @@ use axum::{ use chrono::{DateTime, TimeDelta, Utc}; use libservice::ServiceConnectInfo; use lru::LruCache; +use maud::Markup; use parking_lot::Mutex; use std::{collections::HashMap, num::NonZero, pin::Pin, sync::Arc, time::Instant}; use tower_http::compression::{CompressionLayer, DefaultPredicate}; @@ -15,13 +16,21 @@ use tracing::trace; use crate::{ClientInfo, RequestContext}; +#[derive(Clone)] +pub enum RenderedBody { + Markup(Markup), + Static(&'static [u8]), + Empty, +} + #[derive(Clone)] pub struct Rendered { pub code: StatusCode, pub headers: HeaderMap, - pub body: Vec, + pub body: RenderedBody, pub ttl: Option, + pub immutable: bool, } pub trait Servable: Send + Sync { @@ -209,20 +218,31 @@ impl PageServer { #[expect(clippy::unwrap_used)] let (mut html, expires) = html_expires.unwrap(); - let max_age = match expires { - Some(expires) => (expires - now).num_seconds().max(1), - None => 1, - }; + if !html.headers.contains_key(header::CACHE_CONTROL) { + let max_age = match expires { + Some(expires) => (expires - now).num_seconds().max(1), + None => 1, + }; - #[expect(clippy::unwrap_used)] - html.headers.insert( - header::CACHE_CONTROL, - // immutable; public/private - HeaderValue::from_str(&format!("immutable, public, max-age={}", max_age)).unwrap(), - ); + #[expect(clippy::unwrap_used)] + let mut value = String::new(); + if html.immutable { + value.push_str("immutable, "); + } - html.headers - .insert("Accept-CH", HeaderValue::from_static("Sec-CH-UA-Mobile")); + value.push_str("public, "); + value.push_str(&format!("max-age={}, ", max_age)); + + html.headers.insert( + header::CACHE_CONTROL, + HeaderValue::from_str(&value.trim().trim_end_matches(',')).unwrap(), + ); + } + + if !html.headers.contains_key("Accept-CH") { + html.headers + .insert("Accept-CH", HeaderValue::from_static("Sec-CH-UA-Mobile")); + } trace!( message = "Served route", @@ -233,7 +253,11 @@ impl PageServer { time_ns = start.elapsed().as_nanos() ); - return (html.code, html.headers, html.body).into_response(); + return match html.body { + RenderedBody::Markup(markup) => (html.code, html.headers, markup.0).into_response(), + RenderedBody::Static(data) => (html.code, html.headers, data).into_response(), + RenderedBody::Empty => (html.code, html.headers).into_response(), + }; } pub fn into_router(self: Arc) -> Router<()> { diff --git a/crates/macro/macro-assets/Cargo.toml b/crates/macro/macro-assets/Cargo.toml deleted file mode 100644 index 025e8f9..0000000 --- a/crates/macro/macro-assets/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "macro-assets" -version = { workspace = true } -rust-version = { workspace = true } -edition = { workspace = true } - -[lints] -workspace = true - -[lib] -proc-macro = true - -[dependencies] -syn = { workspace = true } -quote = { workspace = true } diff --git a/crates/macro/macro-assets/src/lib.rs b/crates/macro/macro-assets/src/lib.rs deleted file mode 100644 index dd7c21c..0000000 --- a/crates/macro/macro-assets/src/lib.rs +++ /dev/null @@ -1,309 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{ - Expr, Ident, LitStr, Result, Token, braced, - parse::{Parse, ParseStream}, - parse_macro_input, -}; - -/// A macro for generating static asset handlers with compile-time embedding. -/// -/// This macro generates: -/// - Individual structs for each asset that implement the `assetserver::Asset` trait -/// - Compile-time embedding of asset files using `include_bytes!` -/// - Optionally, an Axum router function that serves all assets -/// -/// # Syntax -/// -/// ```notrust -/// assets! { -/// prefix: "/assets" -/// router: router_function_name() -/// -/// AssetName { -/// source: "path/to/file.ext", -/// target: "/public-url.ext" -/// } -/// -/// AnotherAsset { -/// source: "path/to/another.ext", -/// target: "/another-url.ext" -/// } -/// } -/// ``` -/// -/// # Arguments -/// -/// - `prefix`: The URL prefix for all assets (e.g., "/assets") -/// - `router`: (Optional) The name of a function to generate that returns `(&'static str, Router<()>)` -/// with routes for all assets -/// - Asset blocks: Each block defines an asset with: -/// - A name (identifier) for the generated struct -/// - `source`: The file system path to the asset (relative to the current file) -/// - `target`: The URL path where the asset will be served -/// -/// # Generated Code -/// -/// For each asset, the macro generates: -/// - A struct with the specified name -/// - An `assetserver::Asset` trait implementation containing: -/// - `URL_PREFIX`: The common prefix for all assets -/// - `URL`: The specific URL path for this asset -/// - `BYTES`: The embedded file contents as a byte slice -/// - Documentation showing the original asset definition -/// -/// If `router` is specified, also generates a function that returns an Axum router -/// with all assets mounted at their target URLs. -/// -/// # Example -/// -/// ```notrust -/// assets! { -/// prefix: "/static" -/// router: static_router() -/// -/// Logo { -/// source: "../images/logo.png", -/// target: "/logo.png" -/// } -/// } -/// ``` -/// -/// This generates structs implementing `assetserver::Asset` and optionally a router function: -/// -/// ```notrust -/// pub fn static_router() -> (&'static str, ::axum::Router<()>) { -/// let router = ::axum::Router::new() -/// .route(Logo::URL, ::axum::routing::get(|| async { -/// (::axum::http::StatusCode::OK, Logo::BYTES) -/// })); -/// ("/static", router) -/// } -/// ``` -#[proc_macro] -pub fn assets(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as AssetsInput); - let prefix = &input.prefix; - - let asset_impls = input.assets.iter().map(|asset| { - let name = &asset.name; - let source = &asset.source; - let target = &asset.target; - - // Generate documentation showing the original asset definition - let doc = format!( - "This is an `asset!`\n```notrust\n{} {{\n\tsource: \"{:?}\",\n\ttarget: \"{}\"\n}}\n```", - name, source, target - ); - - quote! { - #[expect(clippy::allow_attributes)] - #[allow(non_camel_case_types)] - #[doc = #doc] - pub struct #name {} - - impl ::assetserver::Asset for #name { - const URL_PREFIX: &'static str = #prefix; - const URL_POSTFIX: &'static str = #target; - const URL: &'static str = concat!(#prefix, #target); - const BYTES: &'static [u8] = #source; - } - } - }); - - // Generate the router function if specified - let router_fn = if let Some(router_name) = &input.router { - let route_definitions = input.assets.iter().map(|asset| { - let name = &asset.name; - - let headers = asset - .headers - .as_ref() - .map(|x| quote! { #x }) - .unwrap_or(quote! { [] }); - - quote! { - .route(#name::URL_POSTFIX, ::axum::routing::get(|| async { - ( - ::axum::http::StatusCode::OK, - #headers, - #name::BYTES - ) - })) - } - }); - - let router_doc = format!( - "Generated router function that serves {} asset(s) with prefix \"{}\"", - input.assets.len(), - prefix - ); - - quote! { - #[doc = #router_doc] - pub fn #router_name() -> (&'static str, ::axum::Router<()>) { - use ::tower_http::compression::{CompressionLayer, DefaultPredicate}; - - let compression: CompressionLayer = CompressionLayer::new() - .br(true) - .deflate(true) - .gzip(true) - .zstd(true) - .compress_when(DefaultPredicate::new()); - - let router = ::axum::Router::new() - #(#route_definitions)* - .layer(compression); - (#prefix, router) - } - } - } else { - quote! {} - }; - - let expanded = quote! { - #(#asset_impls)* - - #router_fn - }; - - TokenStream::from(expanded) -} - -/// Represents the complete input to the `assets!` macro -struct AssetsInput { - prefix: String, - router: Option, - assets: Vec, -} - -/// Represents a single asset definition within the macro -struct AssetDefinition { - name: Ident, - source: Expr, - target: String, - headers: Option, -} - -impl Parse for AssetsInput { - fn parse(input: ParseStream<'_>) -> Result { - // Parse "prefix:" - let _prefix_ident: Ident = input.parse()?; - let _colon: Token![:] = input.parse()?; - let prefix_lit: LitStr = input.parse()?; - let prefix = prefix_lit.value(); - - // Try to parse optional "router:" parameter - let router = if input.peek(Ident) { - let peek_ident: Ident = input.fork().parse()?; - if peek_ident == "router" { - let _router_ident: Ident = input.parse()?; - let _colon: Token![:] = input.parse()?; - let router_name: Ident = input.parse()?; - // Parse the parentheses after the function name - let _paren_content; - syn::parenthesized!(_paren_content in input); - Some(router_name) - } else { - None - } - } else { - None - }; - - let mut assets = Vec::new(); - - // Parse asset definitions until we reach the end - while !input.is_empty() { - let asset = input.parse::()?; - assets.push(asset); - } - - Ok(AssetsInput { - prefix, - router, - assets, - }) - } -} - -impl Parse for AssetDefinition { - fn parse(input: ParseStream<'_>) -> Result { - // Parse the asset name - let name: Ident = input.parse()?; - - // Parse the braced content - let content; - braced!(content in input); - - // Parse fields in any order - let mut source: Option = None; - let mut target: Option = None; - let mut headers: Option = None; - - while !content.is_empty() { - // Parse field name - let field_name: Ident = content.parse()?; - let _colon: Token![:] = content.parse()?; - - // Parse field value based on name - match field_name.to_string().as_str() { - "source" => { - if source.is_some() { - return Err(syn::Error::new( - field_name.span(), - "duplicate 'source' field", - )); - } - source = Some(content.parse()?); - } - "target" => { - if target.is_some() { - return Err(syn::Error::new( - field_name.span(), - "duplicate 'target' field", - )); - } - let target_lit: LitStr = content.parse()?; - target = Some(target_lit.value()); - } - "headers" => { - if headers.is_some() { - return Err(syn::Error::new( - field_name.span(), - "duplicate 'headers' field", - )); - } - headers = Some(content.parse()?); - } - _ => { - return Err(syn::Error::new( - field_name.span(), - format!( - "unknown field '{}', expected 'source', 'target', or 'headers'", - field_name - ), - )); - } - } - - // Parse comma if not at end - if !content.is_empty() { - content.parse::()?; - } - } - - // Validate required fields - let source = source - .ok_or_else(|| syn::Error::new(name.span(), "missing required field 'source'"))?; - let target = target - .ok_or_else(|| syn::Error::new(name.span(), "missing required field 'target'"))?; - - Ok(AssetDefinition { - name, - source, - target, - headers, - }) - } -} diff --git a/crates/service/service-webpage/Cargo.toml b/crates/service/service-webpage/Cargo.toml index 7e2cfdb..8b68e2d 100644 --- a/crates/service/service-webpage/Cargo.toml +++ b/crates/service/service-webpage/Cargo.toml @@ -9,9 +9,7 @@ workspace = true [dependencies] libservice = { workspace = true } -macro-assets = { workspace = true } macro-sass = { workspace = true } -assetserver = { workspace = true } toolbox = { workspace = true } page = { workspace = true } @@ -29,5 +27,4 @@ lazy_static = { workspace = true } toml = { workspace = true } serde = { workspace = true } reqwest = { workspace = true } -tower-http = { workspace = true } tokio = { workspace = true } diff --git a/crates/service/service-webpage/src/pages/handouts.rs b/crates/service/service-webpage/src/pages/handouts.rs index cdadb01..ca4c735 100644 --- a/crates/service/service-webpage/src/pages/handouts.rs +++ b/crates/service/service-webpage/src/pages/handouts.rs @@ -4,7 +4,6 @@ use std::{ time::{Duration, Instant}, }; -use assetserver::Asset; use chrono::{DateTime, TimeDelta, Utc}; use maud::{Markup, PreEscaped, html}; use page::{DeviceType, RequestContext, page::Page}; @@ -18,7 +17,6 @@ use crate::{ misc::FarLink, }, pages::page_wrapper, - routes::assets::Image_Icon, }; #[derive(Debug, Deserialize)] @@ -194,7 +192,7 @@ pub fn handouts() -> Page { let mut meta = meta_from_markdown(&md).unwrap().unwrap(); if meta.image.is_none() { - meta.image = Some(Image_Icon::URL.to_owned()); + meta.image = Some("/assets/img/icon.png".to_owned()); } let html = PreEscaped(md.render()); @@ -202,6 +200,7 @@ pub fn handouts() -> Page { Page { meta, html_ttl: Some(TimeDelta::seconds(300)), + immutable: false, generate_html: Box::new(move |page, ctx| { let html = html.clone(); // TODO: find a way to not clone here diff --git a/crates/service/service-webpage/src/pages/index.rs b/crates/service/service-webpage/src/pages/index.rs index 5b8b3e2..6da22a7 100644 --- a/crates/service/service-webpage/src/pages/index.rs +++ b/crates/service/service-webpage/src/pages/index.rs @@ -1,4 +1,3 @@ -use assetserver::Asset; use maud::html; use page::page::{Page, PageMetadata}; @@ -10,7 +9,6 @@ use crate::{ misc::FarLink, }, pages::page_wrapper, - routes::assets::{Image_Cover, Image_Icon}, }; pub fn index() -> Page { @@ -19,7 +17,7 @@ pub fn index() -> Page { title: "Betalupi: About".into(), author: Some("Mark".into()), description: Some("Description".into()), - image: Some(Image_Icon::URL.into()), + image: Some("/assets/img/icon.png".to_owned()), backlinks: Some(false), }, @@ -30,7 +28,7 @@ pub fn index() -> Page { div { img - src=(Image_Cover::URL) + src="/assets/img/cover-small.jpg" style="float:left;margin:10px 10px 10px 10px;display:block;width:25%;" {} diff --git a/crates/service/service-webpage/src/pages/mod.rs b/crates/service/service-webpage/src/pages/mod.rs index e905657..492a96b 100644 --- a/crates/service/service-webpage/src/pages/mod.rs +++ b/crates/service/service-webpage/src/pages/mod.rs @@ -1,15 +1,11 @@ -use assetserver::Asset; use chrono::TimeDelta; use maud::{DOCTYPE, Markup, PreEscaped, html}; use page::page::{Page, PageMetadata}; use std::pin::Pin; -use crate::{ - components::{ - md::{Markdown, backlinks, meta_from_markdown}, - misc::FarLink, - }, - routes::assets::{Image_Icon, Styles_Main}, +use crate::components::{ + md::{Markdown, backlinks, meta_from_markdown}, + misc::FarLink, }; mod handouts; @@ -26,20 +22,23 @@ pub fn links() -> Page { http://www.3dprintmath.com/ */ - page_from_markdown(include_str!("links.md"), Some(Image_Icon::URL.to_owned())) + page_from_markdown( + include_str!("links.md"), + Some("/assets/img/icon.png".to_string()), + ) } pub fn betalupi() -> Page { page_from_markdown( include_str!("betalupi.md"), - Some(Image_Icon::URL.to_owned()), + Some("/assets/img/icon.png".to_string()), ) } pub fn htwah_typesetting() -> Page { page_from_markdown( include_str!("htwah-typesetting.md"), - Some(Image_Icon::URL.to_owned()), + Some("/assets/img/icon.png".to_string()), ) } @@ -66,6 +65,7 @@ fn page_from_markdown(md: impl Into, default_image: Option) -> P Page { meta, + immutable: true, html_ttl: Some(TimeDelta::seconds(60 * 24 * 30)), generate_html: Box::new(move |page, ctx| { @@ -104,7 +104,7 @@ pub fn page_wrapper<'a>( meta content="text/html; charset=UTF-8" http-equiv="content-type" {} meta property="og:type" content="website" {} - link rel="stylesheet" href=(Styles_Main::URL) {} + link rel="stylesheet" href=("/assets/css/main.css") {} (&meta) title { (PreEscaped(meta.title.clone())) } diff --git a/crates/service/service-webpage/src/routes/assets.rs b/crates/service/service-webpage/src/routes/assets.rs deleted file mode 100644 index 2ea9480..0000000 --- a/crates/service/service-webpage/src/routes/assets.rs +++ /dev/null @@ -1,206 +0,0 @@ -use assetserver::Asset; -use axum::http::header; -use macro_assets::assets; -use macro_sass::sass; -use toolbox::mime::MimeType; - -assets! { - prefix: "/assets" - router: asset_router() - - // - // MARK: styles - // - - Styles_Main { - source: sass!("css/main.scss").as_bytes(), - target: "/css/main.css", - headers: [ - (header::CONTENT_TYPE, "text/css") - ] - } - - // - // MARK: images - // - - Image_Cover { - source: include_bytes!("../../assets/images/cover-small.jpg"), - target: "/img/face.jpg", - headers: [ - (header::CONTENT_TYPE, "image/jpg") - ] - } - - Image_Betalupi { - source: include_bytes!("../../assets/images/betalupi-map.png"), - target: "/img/betalupi.png", - headers: [ - (header::CONTENT_TYPE, "image/png") - ] - } - - Image_Icon { - source: include_bytes!("../../assets/images/icon.png"), - target: "/img/icon.png", - headers: [ - (header::CONTENT_TYPE, "image/png") - ] - } - - // - // MARK: fonts - // - - FiraCode_Bold_woff2 { - source: include_bytes!("../../assets/fonts/fira/FiraCode-Bold.woff2"), - target: "/fonts/FiraCode-Bold.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - FiraCode_Light_woff2 { - source: include_bytes!("../../assets/fonts/fira/FiraCode-Light.woff2"), - target: "/fonts/FiraCode-Light.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - FiraCode_Medium_woff2 { - source: include_bytes!("../../assets/fonts/fira/FiraCode-Medium.woff2"), - target: "/fonts/FiraCode-Medium.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - FiraCode_Regular_woff2 { - source: include_bytes!("../../assets/fonts/fira/FiraCode-Regular.woff2"), - target: "/fonts/FiraCode-Regular.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - FiraCode_SemiBold_woff2 { - source: include_bytes!("../../assets/fonts/fira/FiraCode-SemiBold.woff2"), - target: "/fonts/FiraCode-SemiBold.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - FiraCode_VF_woff2 { - source: include_bytes!("../../assets/fonts/fira/FiraCode-VF.woff2"), - target: "/fonts/FiraCode-VF.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - // - // MARK: icons - // - - Fa_Brands_woff2 { - source: include_bytes!("../../assets/fonts/fa/fa-brands-400.woff2"), - target: "/fonts/fa/fa-brands-400.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - Fa_Regular_woff2 { - source: include_bytes!("../../assets/fonts/fa/fa-regular-400.woff2"), - target: "/fonts/fa/fa-regular-400.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - Fa_Solid_woff2 { - source: include_bytes!("../../assets/fonts/fa/fa-solid-900.woff2"), - target: "/fonts/fa/fa-solid-900.woff2", - headers: [ - (header::CONTENT_TYPE, "application/font-woff2") - ] - } - - Fa_Brands_ttf { - source: include_bytes!("../../assets/fonts/fa/fa-brands-400.ttf"), - target: "/fonts/fa/fa-brands-400.ttf", - headers: [ - (header::CONTENT_TYPE, "application/font-ttf") - ] - } - - Fa_Regular_ttf { - source: include_bytes!("../../assets/fonts/fa/fa-regular-400.ttf"), - target: "/fonts/fa/fa-regular-400.ttf", - headers: [ - (header::CONTENT_TYPE, "application/font-ttf") - ] - } - - Fa_Solid_ttf { - source: include_bytes!("../../assets/fonts/fa/fa-solid-900.ttf"), - target: "/fonts/fa/fa-solid-900.ttf", - headers: [ - (header::CONTENT_TYPE, "application/font-ttf") - ] - } - - // - // MARK: htwah - // - - Htwah_Definitions { - source: include_bytes!("../../assets/htwah/definitions.pdf"), - target: "/htwah/definitions.pdf", - headers: [ - (header::CONTENT_TYPE, MimeType::Pdf.to_string()) - ] - } - - Htwah_Numbering { - source: include_bytes!("../../assets/htwah/numbering.pdf"), - target: "/htwah/numbering.pdf", - headers: [ - (header::CONTENT_TYPE, MimeType::Pdf.to_string()) - ] - } - - Htwah_SolsA { - source: include_bytes!("../../assets/htwah/sols-a.pdf"), - target: "/htwah/sols-a.pdf", - headers: [ - (header::CONTENT_TYPE, MimeType::Pdf.to_string()) - ] - } - - Htwah_SolsB { - source: include_bytes!("../../assets/htwah/sols-b.pdf"), - target: "/htwah/sols-b.pdf", - headers: [ - (header::CONTENT_TYPE, MimeType::Pdf.to_string()) - ] - } - - Htwah_SpacingA { - source: include_bytes!("../../assets/htwah/spacing-a.pdf"), - target: "/htwah/spacing-a.pdf", - headers: [ - (header::CONTENT_TYPE, MimeType::Pdf.to_string()) - ] - } - - Htwah_SpacingB { - source: include_bytes!("../../assets/htwah/spacing-b.pdf"), - target: "/htwah/spacing-b.pdf", - headers: [ - (header::CONTENT_TYPE, MimeType::Pdf.to_string()) - ] - } -} diff --git a/crates/service/service-webpage/src/routes/mod.rs b/crates/service/service-webpage/src/routes/mod.rs index 6212590..78d254c 100644 --- a/crates/service/service-webpage/src/routes/mod.rs +++ b/crates/service/service-webpage/src/routes/mod.rs @@ -1,19 +1,13 @@ use axum::Router; -use page::{PageServer, redirect::Redirect}; +use macro_sass::sass; +use page::{PageServer, asset::StaticAsset, redirect::Redirect}; use std::sync::Arc; -use tracing::info; +use toolbox::mime::MimeType; use crate::pages; -pub mod assets; - pub(super) fn router() -> Router<()> { - let (asset_prefix, asset_router) = assets::asset_router(); - info!("Serving assets at {asset_prefix}"); - - let router = build_server().into_router(); - - Router::new().merge(router).nest(asset_prefix, asset_router) + build_server().into_router() } fn build_server() -> Arc { @@ -26,7 +20,171 @@ fn build_server() -> Arc { .add_page("/whats-a-betalupi", pages::betalupi()) .add_page("/handouts", pages::handouts()) .add_page("/htwah", Redirect::new("/handouts").unwrap()) - .add_page("/htwah/typesetting", pages::htwah_typesetting()); + .add_page("/htwah/typesetting", pages::htwah_typesetting()) + // + .add_page( + "/assets/css/main.css", + StaticAsset { + bytes: sass!("css/main.scss").as_bytes(), + mime: MimeType::Css, + }, + ) + .add_page( + "/assets/img/cover-small.jpg", + StaticAsset { + bytes: include_bytes!("../../assets/images/cover-small.jpg"), + mime: MimeType::Css, + }, + ) + .add_page( + "/assets/img/betalupi.png", + StaticAsset { + bytes: include_bytes!("../../assets/images/betalupi-map.png"), + mime: MimeType::Css, + }, + ) + .add_page( + "/assets/img/icon.png", + StaticAsset { + bytes: include_bytes!("../../assets/images/icon.png"), + mime: MimeType::Css, + }, + ) + // + // MARK: fonts + // + .add_page( + "/assets/fonts/FiraCode-Bold.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fira/FiraCode-Bold.woff2"), + mime: MimeType::Woff2, + }, + ) + .add_page( + "/assets/fonts/FiraCode-Light.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fira/FiraCode-Light.woff2"), + mime: MimeType::Woff2, + }, + ) + .add_page( + "/assets/fonts/FiraCode-Medium.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fira/FiraCode-Medium.woff2"), + mime: MimeType::Woff2, + }, + ) + .add_page( + "/assets/fonts/FiraCode-Regular.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fira/FiraCode-Regular.woff2"), + mime: MimeType::Woff2, + }, + ) + .add_page( + "/assets/fonts/FiraCode-SemiBold.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fira/FiraCode-SemiBold.woff2"), + mime: MimeType::Woff2, + }, + ) + .add_page( + "/assets/fonts/FiraCode-VF.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fira/FiraCode-VF.woff2"), + mime: MimeType::Woff2, + }, + ) + // + // MARK: icons + // + .add_page( + "/assets/fonts/fa/fa-brands-400.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fa/fa-brands-400.woff2"), + mime: MimeType::Woff2, + }, + ) + .add_page( + "/assets/fonts/fa/fa-regular-400.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fa/fa-regular-400.woff2"), + mime: MimeType::Woff2, + }, + ) + .add_page( + "/assets/fonts/fa/fa-solid-900.woff2", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fa/fa-solid-900.woff2"), + mime: MimeType::Woff2, + }, + ) + .add_page( + "/assets/fonts/fa/fa-brands-400.ttf", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fa/fa-brands-400.ttf"), + mime: MimeType::Ttf, + }, + ) + .add_page( + "/assets/fonts/fa/fa-regular-400.ttf", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fa/fa-regular-400.ttf"), + mime: MimeType::Ttf, + }, + ) + .add_page( + "/assets/fonts/fa/fa-solid-900.ttf", + StaticAsset { + bytes: include_bytes!("../../assets/fonts/fa/fa-solid-900.ttf"), + mime: MimeType::Ttf, + }, + ) + // + // MARK: htwah + // + .add_page( + "/assets/htwah/definitions.pdf", + StaticAsset { + bytes: include_bytes!("../../assets/htwah/definitions.pdf"), + mime: MimeType::Pdf, + }, + ) + .add_page( + "/assets/htwah/numbering.pdf", + StaticAsset { + bytes: include_bytes!("../../assets/htwah/numbering.pdf"), + mime: MimeType::Pdf, + }, + ) + .add_page( + "/assets/htwah/sols-a.pdf", + StaticAsset { + bytes: include_bytes!("../../assets/htwah/sols-a.pdf"), + mime: MimeType::Pdf, + }, + ) + .add_page( + "/assets/htwah/sols-b.pdf", + StaticAsset { + bytes: include_bytes!("../../assets/htwah/sols-b.pdf"), + mime: MimeType::Pdf, + }, + ) + .add_page( + "/assets/htwah/spacing-a.pdf", + StaticAsset { + bytes: include_bytes!("../../assets/htwah/spacing-a.pdf"), + mime: MimeType::Pdf, + }, + ) + .add_page( + "/assets/htwah/spacing-b.pdf", + StaticAsset { + bytes: include_bytes!("../../assets/htwah/spacing-b.pdf"), + mime: MimeType::Pdf, + }, + ); server }