Merge asset and page server
This commit is contained in:
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -139,10 +139,6 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f8ebf5827e4ac4fd5946560e6a99776ea73b596d80898f357007317a7141e47"
|
checksum = "3f8ebf5827e4ac4fd5946560e6a99776ea73b596d80898f357007317a7141e47"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "assetserver"
|
|
||||||
version = "0.0.1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ast_node"
|
name = "ast_node"
|
||||||
version = "5.0.0"
|
version = "5.0.0"
|
||||||
@@ -1450,14 +1446,6 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "macro-assets"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.108",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "macro-sass"
|
name = "macro-sass"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
@@ -1772,6 +1760,7 @@ dependencies = [
|
|||||||
"maud",
|
"maud",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
"serde",
|
||||||
|
"toolbox",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
@@ -2504,13 +2493,11 @@ dependencies = [
|
|||||||
name = "service-webpage"
|
name = "service-webpage"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assetserver",
|
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
"emojis",
|
"emojis",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libservice",
|
"libservice",
|
||||||
"macro-assets",
|
|
||||||
"macro-sass",
|
"macro-sass",
|
||||||
"markdown-it",
|
"markdown-it",
|
||||||
"maud",
|
"maud",
|
||||||
@@ -2523,7 +2510,6 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"toolbox",
|
"toolbox",
|
||||||
"tower-http",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -64,9 +64,7 @@ type_complexity = "allow"
|
|||||||
#
|
#
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
macro-assets = { path = "crates/macro/macro-assets" }
|
|
||||||
macro-sass = { path = "crates/macro/macro-sass" }
|
macro-sass = { path = "crates/macro/macro-sass" }
|
||||||
assetserver = { path = "crates/lib/assetserver" }
|
|
||||||
libservice = { path = "crates/lib/libservice" }
|
libservice = { path = "crates/lib/libservice" }
|
||||||
toolbox = { path = "crates/lib/toolbox" }
|
toolbox = { path = "crates/lib/toolbox" }
|
||||||
page = { path = "crates/lib/page" }
|
page = { path = "crates/lib/page" }
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "assetserver"
|
|
||||||
version = { workspace = true }
|
|
||||||
rust-version = { workspace = true }
|
|
||||||
edition = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ edition = { workspace = true }
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
toolbox = { workspace = true }
|
||||||
libservice = { workspace = true }
|
libservice = { workspace = true }
|
||||||
|
|
||||||
axum = { workspace = true }
|
axum = { workspace = true }
|
||||||
|
|||||||
44
crates/lib/page/src/servable/asset.rs
Normal file
44
crates/lib/page/src/servable/asset.rs
Normal file
@@ -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<Box<dyn Future<Output = crate::Rendered> + '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,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
pub mod asset;
|
||||||
pub mod page;
|
pub mod page;
|
||||||
pub mod redirect;
|
pub mod redirect;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use maud::{Markup, Render, html};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use crate::{Rendered, RequestContext, Servable};
|
use crate::{Rendered, RenderedBody, RequestContext, Servable};
|
||||||
|
|
||||||
//
|
//
|
||||||
// MARK: metadata
|
// MARK: metadata
|
||||||
@@ -67,6 +67,7 @@ impl Render for PageMetadata {
|
|||||||
// Some HTML
|
// Some HTML
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
pub meta: PageMetadata,
|
pub meta: PageMetadata,
|
||||||
|
pub immutable: bool,
|
||||||
|
|
||||||
/// How long this page's html may be cached.
|
/// How long this page's html may be cached.
|
||||||
/// This controls the maximum age of a page shown to the user.
|
/// This controls the maximum age of a page shown to the user.
|
||||||
@@ -94,10 +95,11 @@ impl Default for Page {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Page {
|
Page {
|
||||||
meta: Default::default(),
|
meta: Default::default(),
|
||||||
html_ttl: Some(TimeDelta::seconds(60 * 24 * 30)),
|
html_ttl: Some(TimeDelta::seconds(60 * 60 * 24 * 30)),
|
||||||
//css_ttl: Duration::from_secs(60 * 24 * 30),
|
//css_ttl: Duration::from_secs(60 * 60 * 24 * 30),
|
||||||
//generate_css: None,
|
//generate_css: None,
|
||||||
generate_html: Box::new(|_, _| Box::pin(async { html!() })),
|
generate_html: Box::new(|_, _| Box::pin(async { html!() })),
|
||||||
|
immutable: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,8 +127,9 @@ impl Servable for Page {
|
|||||||
return Rendered {
|
return Rendered {
|
||||||
code: StatusCode::OK,
|
code: StatusCode::OK,
|
||||||
headers,
|
headers,
|
||||||
body: html.0.into_bytes(),
|
body: RenderedBody::Markup(html),
|
||||||
ttl: self.html_ttl,
|
ttl: self.html_ttl,
|
||||||
|
immutable: self.immutable,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use axum::http::{
|
|||||||
header::{self, InvalidHeaderValue},
|
header::{self, InvalidHeaderValue},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Rendered, RequestContext, Servable};
|
use crate::{Rendered, RenderedBody, RequestContext, Servable};
|
||||||
|
|
||||||
pub struct Redirect {
|
pub struct Redirect {
|
||||||
to: HeaderValue,
|
to: HeaderValue,
|
||||||
@@ -31,8 +31,9 @@ impl Servable for Redirect {
|
|||||||
return Rendered {
|
return Rendered {
|
||||||
code: StatusCode::PERMANENT_REDIRECT,
|
code: StatusCode::PERMANENT_REDIRECT,
|
||||||
headers,
|
headers,
|
||||||
body: Vec::new(),
|
body: RenderedBody::Empty,
|
||||||
ttl: None,
|
ttl: None,
|
||||||
|
immutable: true,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use axum::{
|
|||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
use libservice::ServiceConnectInfo;
|
use libservice::ServiceConnectInfo;
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
|
use maud::Markup;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{collections::HashMap, num::NonZero, pin::Pin, sync::Arc, time::Instant};
|
use std::{collections::HashMap, num::NonZero, pin::Pin, sync::Arc, time::Instant};
|
||||||
use tower_http::compression::{CompressionLayer, DefaultPredicate};
|
use tower_http::compression::{CompressionLayer, DefaultPredicate};
|
||||||
@@ -15,13 +16,21 @@ use tracing::trace;
|
|||||||
|
|
||||||
use crate::{ClientInfo, RequestContext};
|
use crate::{ClientInfo, RequestContext};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum RenderedBody {
|
||||||
|
Markup(Markup),
|
||||||
|
Static(&'static [u8]),
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Rendered {
|
pub struct Rendered {
|
||||||
pub code: StatusCode,
|
pub code: StatusCode,
|
||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
pub body: Vec<u8>,
|
pub body: RenderedBody,
|
||||||
|
|
||||||
pub ttl: Option<TimeDelta>,
|
pub ttl: Option<TimeDelta>,
|
||||||
|
pub immutable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Servable: Send + Sync {
|
pub trait Servable: Send + Sync {
|
||||||
@@ -209,20 +218,31 @@ impl PageServer {
|
|||||||
#[expect(clippy::unwrap_used)]
|
#[expect(clippy::unwrap_used)]
|
||||||
let (mut html, expires) = html_expires.unwrap();
|
let (mut html, expires) = html_expires.unwrap();
|
||||||
|
|
||||||
|
if !html.headers.contains_key(header::CACHE_CONTROL) {
|
||||||
let max_age = match expires {
|
let max_age = match expires {
|
||||||
Some(expires) => (expires - now).num_seconds().max(1),
|
Some(expires) => (expires - now).num_seconds().max(1),
|
||||||
None => 1,
|
None => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[expect(clippy::unwrap_used)]
|
#[expect(clippy::unwrap_used)]
|
||||||
|
let mut value = String::new();
|
||||||
|
if html.immutable {
|
||||||
|
value.push_str("immutable, ");
|
||||||
|
}
|
||||||
|
|
||||||
|
value.push_str("public, ");
|
||||||
|
value.push_str(&format!("max-age={}, ", max_age));
|
||||||
|
|
||||||
html.headers.insert(
|
html.headers.insert(
|
||||||
header::CACHE_CONTROL,
|
header::CACHE_CONTROL,
|
||||||
// immutable; public/private
|
HeaderValue::from_str(&value.trim().trim_end_matches(',')).unwrap(),
|
||||||
HeaderValue::from_str(&format!("immutable, public, max-age={}", max_age)).unwrap(),
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !html.headers.contains_key("Accept-CH") {
|
||||||
html.headers
|
html.headers
|
||||||
.insert("Accept-CH", HeaderValue::from_static("Sec-CH-UA-Mobile"));
|
.insert("Accept-CH", HeaderValue::from_static("Sec-CH-UA-Mobile"));
|
||||||
|
}
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
message = "Served route",
|
message = "Served route",
|
||||||
@@ -233,7 +253,11 @@ impl PageServer {
|
|||||||
time_ns = start.elapsed().as_nanos()
|
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<Self>) -> Router<()> {
|
pub fn into_router(self: Arc<Self>) -> Router<()> {
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
@@ -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<Ident>,
|
|
||||||
assets: Vec<AssetDefinition>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a single asset definition within the macro
|
|
||||||
struct AssetDefinition {
|
|
||||||
name: Ident,
|
|
||||||
source: Expr,
|
|
||||||
target: String,
|
|
||||||
headers: Option<Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for AssetsInput {
|
|
||||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
|
||||||
// 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::<AssetDefinition>()?;
|
|
||||||
assets.push(asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(AssetsInput {
|
|
||||||
prefix,
|
|
||||||
router,
|
|
||||||
assets,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for AssetDefinition {
|
|
||||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
|
||||||
// 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<Expr> = None;
|
|
||||||
let mut target: Option<String> = None;
|
|
||||||
let mut headers: Option<Expr> = 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::<Token![,]>()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,7 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libservice = { workspace = true }
|
libservice = { workspace = true }
|
||||||
macro-assets = { workspace = true }
|
|
||||||
macro-sass = { workspace = true }
|
macro-sass = { workspace = true }
|
||||||
assetserver = { workspace = true }
|
|
||||||
toolbox = { workspace = true }
|
toolbox = { workspace = true }
|
||||||
page = { workspace = true }
|
page = { workspace = true }
|
||||||
|
|
||||||
@@ -29,5 +27,4 @@ lazy_static = { workspace = true }
|
|||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
tower-http = { workspace = true }
|
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use std::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use assetserver::Asset;
|
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
use maud::{Markup, PreEscaped, html};
|
use maud::{Markup, PreEscaped, html};
|
||||||
use page::{DeviceType, RequestContext, page::Page};
|
use page::{DeviceType, RequestContext, page::Page};
|
||||||
@@ -18,7 +17,6 @@ use crate::{
|
|||||||
misc::FarLink,
|
misc::FarLink,
|
||||||
},
|
},
|
||||||
pages::page_wrapper,
|
pages::page_wrapper,
|
||||||
routes::assets::Image_Icon,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -194,7 +192,7 @@ pub fn handouts() -> Page {
|
|||||||
let mut meta = meta_from_markdown(&md).unwrap().unwrap();
|
let mut meta = meta_from_markdown(&md).unwrap().unwrap();
|
||||||
|
|
||||||
if meta.image.is_none() {
|
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());
|
let html = PreEscaped(md.render());
|
||||||
@@ -202,6 +200,7 @@ pub fn handouts() -> Page {
|
|||||||
Page {
|
Page {
|
||||||
meta,
|
meta,
|
||||||
html_ttl: Some(TimeDelta::seconds(300)),
|
html_ttl: Some(TimeDelta::seconds(300)),
|
||||||
|
immutable: false,
|
||||||
|
|
||||||
generate_html: Box::new(move |page, ctx| {
|
generate_html: Box::new(move |page, ctx| {
|
||||||
let html = html.clone(); // TODO: find a way to not clone here
|
let html = html.clone(); // TODO: find a way to not clone here
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use assetserver::Asset;
|
|
||||||
use maud::html;
|
use maud::html;
|
||||||
use page::page::{Page, PageMetadata};
|
use page::page::{Page, PageMetadata};
|
||||||
|
|
||||||
@@ -10,7 +9,6 @@ use crate::{
|
|||||||
misc::FarLink,
|
misc::FarLink,
|
||||||
},
|
},
|
||||||
pages::page_wrapper,
|
pages::page_wrapper,
|
||||||
routes::assets::{Image_Cover, Image_Icon},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn index() -> Page {
|
pub fn index() -> Page {
|
||||||
@@ -19,7 +17,7 @@ pub fn index() -> Page {
|
|||||||
title: "Betalupi: About".into(),
|
title: "Betalupi: About".into(),
|
||||||
author: Some("Mark".into()),
|
author: Some("Mark".into()),
|
||||||
description: Some("Description".into()),
|
description: Some("Description".into()),
|
||||||
image: Some(Image_Icon::URL.into()),
|
image: Some("/assets/img/icon.png".to_owned()),
|
||||||
backlinks: Some(false),
|
backlinks: Some(false),
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ pub fn index() -> Page {
|
|||||||
|
|
||||||
div {
|
div {
|
||||||
img
|
img
|
||||||
src=(Image_Cover::URL)
|
src="/assets/img/cover-small.jpg"
|
||||||
style="float:left;margin:10px 10px 10px 10px;display:block;width:25%;"
|
style="float:left;margin:10px 10px 10px 10px;display:block;width:25%;"
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
use assetserver::Asset;
|
|
||||||
use chrono::TimeDelta;
|
use chrono::TimeDelta;
|
||||||
use maud::{DOCTYPE, Markup, PreEscaped, html};
|
use maud::{DOCTYPE, Markup, PreEscaped, html};
|
||||||
use page::page::{Page, PageMetadata};
|
use page::page::{Page, PageMetadata};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use crate::{
|
use crate::components::{
|
||||||
components::{
|
|
||||||
md::{Markdown, backlinks, meta_from_markdown},
|
md::{Markdown, backlinks, meta_from_markdown},
|
||||||
misc::FarLink,
|
misc::FarLink,
|
||||||
},
|
|
||||||
routes::assets::{Image_Icon, Styles_Main},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod handouts;
|
mod handouts;
|
||||||
@@ -26,20 +22,23 @@ pub fn links() -> Page {
|
|||||||
http://www.3dprintmath.com/
|
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 {
|
pub fn betalupi() -> Page {
|
||||||
page_from_markdown(
|
page_from_markdown(
|
||||||
include_str!("betalupi.md"),
|
include_str!("betalupi.md"),
|
||||||
Some(Image_Icon::URL.to_owned()),
|
Some("/assets/img/icon.png".to_string()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn htwah_typesetting() -> Page {
|
pub fn htwah_typesetting() -> Page {
|
||||||
page_from_markdown(
|
page_from_markdown(
|
||||||
include_str!("htwah-typesetting.md"),
|
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<String>, default_image: Option<String>) -> P
|
|||||||
|
|
||||||
Page {
|
Page {
|
||||||
meta,
|
meta,
|
||||||
|
immutable: true,
|
||||||
|
|
||||||
html_ttl: Some(TimeDelta::seconds(60 * 24 * 30)),
|
html_ttl: Some(TimeDelta::seconds(60 * 24 * 30)),
|
||||||
generate_html: Box::new(move |page, ctx| {
|
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 content="text/html; charset=UTF-8" http-equiv="content-type" {}
|
||||||
meta property="og:type" content="website" {}
|
meta property="og:type" content="website" {}
|
||||||
|
|
||||||
link rel="stylesheet" href=(Styles_Main::URL) {}
|
link rel="stylesheet" href=("/assets/css/main.css") {}
|
||||||
|
|
||||||
(&meta)
|
(&meta)
|
||||||
title { (PreEscaped(meta.title.clone())) }
|
title { (PreEscaped(meta.title.clone())) }
|
||||||
|
|||||||
@@ -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())
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
use axum::Router;
|
use axum::Router;
|
||||||
use page::{PageServer, redirect::Redirect};
|
use macro_sass::sass;
|
||||||
|
use page::{PageServer, asset::StaticAsset, redirect::Redirect};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::info;
|
use toolbox::mime::MimeType;
|
||||||
|
|
||||||
use crate::pages;
|
use crate::pages;
|
||||||
|
|
||||||
pub mod assets;
|
|
||||||
|
|
||||||
pub(super) fn router() -> Router<()> {
|
pub(super) fn router() -> Router<()> {
|
||||||
let (asset_prefix, asset_router) = assets::asset_router();
|
build_server().into_router()
|
||||||
info!("Serving assets at {asset_prefix}");
|
|
||||||
|
|
||||||
let router = build_server().into_router();
|
|
||||||
|
|
||||||
Router::new().merge(router).nest(asset_prefix, asset_router)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_server() -> Arc<PageServer> {
|
fn build_server() -> Arc<PageServer> {
|
||||||
@@ -26,7 +20,171 @@ fn build_server() -> Arc<PageServer> {
|
|||||||
.add_page("/whats-a-betalupi", pages::betalupi())
|
.add_page("/whats-a-betalupi", pages::betalupi())
|
||||||
.add_page("/handouts", pages::handouts())
|
.add_page("/handouts", pages::handouts())
|
||||||
.add_page("/htwah", Redirect::new("/handouts").unwrap())
|
.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
|
server
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user