Replace hardcoded static files with a static endpoint
This commit is contained in:
parent
6882600336
commit
1103c89428
35 changed files with 206 additions and 145 deletions
|
|
@ -1,3 +1,4 @@
|
|||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
single-char-binding-names-threshold = 2
|
||||
upper-case-acronyms-aggressive = true
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mod handlers {
|
|||
pub mod navigation;
|
||||
pub mod fixed;
|
||||
pub mod error;
|
||||
pub mod mime;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -32,36 +33,7 @@ pub fn new(graph: Graph) -> Router {
|
|||
.route("/graph/{format}", get(handlers::fixed::serial))
|
||||
.route("/search", get(handlers::navigation::search))
|
||||
.route("/redirect", get(handlers::navigation::redirect))
|
||||
.route(
|
||||
"/static/style.css",
|
||||
get(|| handlers::fixed::file("./static/style.css", "text/css")),
|
||||
)
|
||||
.route(
|
||||
"/static/fonts/sans",
|
||||
get(|| handlers::fixed::file("./static/fonts/sans", "")),
|
||||
)
|
||||
.route(
|
||||
"/static/fonts/serifed",
|
||||
get(|| handlers::fixed::file("./static/fonts/serifed", "")),
|
||||
)
|
||||
.route(
|
||||
"/static/fonts/mono",
|
||||
get(|| handlers::fixed::file("./static/fonts/mono", "")),
|
||||
)
|
||||
.route(
|
||||
"/static/fonts/title",
|
||||
get(|| handlers::fixed::file("./static/fonts/title", "")),
|
||||
)
|
||||
.route(
|
||||
"/static/fonts/prose",
|
||||
get(|| handlers::fixed::file("./static/fonts/prose", "")),
|
||||
)
|
||||
.route(
|
||||
"/static/favicon.svg",
|
||||
get(|| {
|
||||
handlers::fixed::file("./static/favicon.svg", "image/svg+xml")
|
||||
}),
|
||||
);
|
||||
.route("/static/{*path}", get(handlers::fixed::file));
|
||||
|
||||
if state.graph.meta.config.tree {
|
||||
router = router.route("/tree", get(handlers::navigation::tree));
|
||||
|
|
@ -127,8 +99,8 @@ mod tests {
|
|||
"/tree",
|
||||
"/data",
|
||||
"/node/Syntax",
|
||||
"/static/style.css",
|
||||
"/static/favicon.svg",
|
||||
"/static/assets/style.css",
|
||||
"/static/assets/favicon.svg",
|
||||
"/graph/json",
|
||||
"/graph/toml",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,24 +1,44 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
extract::{Path, State},
|
||||
http::{HeaderValue, Response, StatusCode, header},
|
||||
{
|
||||
body::Body,
|
||||
extract::{Path, State},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
graph::{Format, Graph, SerialErrorCause},
|
||||
router::{GlobalState, handlers},
|
||||
router::handlers::mime::Mime,
|
||||
};
|
||||
|
||||
/// # Panics
|
||||
/// Will panic if file read fails.
|
||||
#[expect(clippy::unused_async)]
|
||||
pub async fn file(file_path: &str, content_type: &str) -> Response<Body> {
|
||||
pub async fn file(
|
||||
Path(path): Path<String>,
|
||||
State(state): State<GlobalState>,
|
||||
) -> Response<Body> {
|
||||
let instant = now();
|
||||
let content = match std::fs::read(file_path) {
|
||||
let target = format!("static/public/{path}");
|
||||
let content = match std::fs::read(&target) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
panic!("Failed to read {file_path} contents: {e}")
|
||||
let mut error_message = String::from(
|
||||
"The requested file does not exist, the server does not have \
|
||||
permission to access it or a filesystem error ocurred.",
|
||||
);
|
||||
if log::env_level() >= DEBUG {
|
||||
error_message = format!(
|
||||
"<p>{error_message}</p>\
|
||||
<p>Targeted path: <code>{target}</code></p>\
|
||||
<p>Error message:</p> <pre>{e}</pre>"
|
||||
);
|
||||
}
|
||||
log!(ERROR, "{error_message}");
|
||||
return super::error::by_code(
|
||||
Some(404),
|
||||
Some(&error_message),
|
||||
&state.graph,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -26,19 +46,20 @@ pub async fn file(file_path: &str, content_type: &str) -> Response<Body> {
|
|||
*response.status_mut() = StatusCode::OK;
|
||||
let header = header::CONTENT_TYPE;
|
||||
|
||||
if let Ok(header_value) = HeaderValue::from_str(content_type) {
|
||||
let content_type = Mime::guess(&path);
|
||||
|
||||
if let Ok(header_value) =
|
||||
HeaderValue::from_str(&String::from(content_type.clone()))
|
||||
{
|
||||
response.headers_mut().append(header, header_value);
|
||||
} else {
|
||||
log!(
|
||||
WARN,
|
||||
"Failed to create content type header value from {content_type}"
|
||||
"Failed to create content type header value from {content_type:?}"
|
||||
);
|
||||
}
|
||||
|
||||
tlog!(
|
||||
&instant,
|
||||
"Assembled response for {content_type} {file_path}"
|
||||
);
|
||||
tlog!(&instant, "Assembled response for {content_type:?} {path}");
|
||||
response
|
||||
}
|
||||
|
||||
|
|
@ -147,30 +168,4 @@ mod tests {
|
|||
== "application/json"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn file_valid_header() {
|
||||
let payload = "y1mgMhjeIMFsRNZ1tskP52DfWuvhvbRP";
|
||||
let response = file("./static/graph.toml", payload).await;
|
||||
assert_eq!(
|
||||
response.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn file_invalid_header() {
|
||||
let response = file("./static/graph.toml", "\n").await;
|
||||
println!("{response:#?}");
|
||||
assert!(response.headers().get(header::CONTENT_TYPE).is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(
|
||||
expected = "Failed to read IvnhZhdHb1xDnUw4hYDDNIERoaOojkiu \
|
||||
contents: No such file or directory (os error 2)"
|
||||
)]
|
||||
async fn file_invalid_path() {
|
||||
drop(file("IvnhZhdHb1xDnUw4hYDDNIERoaOojkiu", "text/plain").await);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
94
src/router/handlers/mime.rs
Normal file
94
src/router/handlers/mime.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum Mime {
|
||||
Txt,
|
||||
Csv,
|
||||
Css,
|
||||
Ttf,
|
||||
Otf,
|
||||
Woff,
|
||||
Woff2,
|
||||
Svg,
|
||||
Ico,
|
||||
Jpeg,
|
||||
Png,
|
||||
Apng,
|
||||
Gif,
|
||||
Webp,
|
||||
Avif,
|
||||
Toml,
|
||||
Xml,
|
||||
Json,
|
||||
Js,
|
||||
Pdf,
|
||||
Epub,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<&str> for Mime {
|
||||
fn from(extension: &str) -> Mime {
|
||||
match extension {
|
||||
"txt" => Mime::Txt,
|
||||
"csv" => Mime::Csv,
|
||||
"css" => Mime::Css,
|
||||
"ttf" => Mime::Ttf,
|
||||
"otf" => Mime::Otf,
|
||||
"woff" => Mime::Woff,
|
||||
"woff2" => Mime::Woff2,
|
||||
"svg" => Mime::Svg,
|
||||
"ico" => Mime::Ico,
|
||||
"jpeg" => Mime::Jpeg,
|
||||
"png" => Mime::Png,
|
||||
"apng" => Mime::Apng,
|
||||
"gif" => Mime::Gif,
|
||||
"webp" => Mime::Webp,
|
||||
"avif" => Mime::Avif,
|
||||
"toml" => Mime::Toml,
|
||||
"xml" => Mime::Xml,
|
||||
"json" => Mime::Json,
|
||||
"js" => Mime::Js,
|
||||
"pdf" => Mime::Pdf,
|
||||
"epub" => Mime::Epub,
|
||||
_ => Mime::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mime> for String {
|
||||
fn from(mime: Mime) -> String {
|
||||
let s = match mime {
|
||||
Mime::Txt => "text/plain",
|
||||
Mime::Csv => "text/csv",
|
||||
Mime::Css => "text/css",
|
||||
Mime::Ttf => "font/ttf",
|
||||
Mime::Otf => "font/otf",
|
||||
Mime::Woff => "font/woff",
|
||||
Mime::Woff2 => "font/woff2",
|
||||
Mime::Svg => "image/svg+xml",
|
||||
Mime::Ico => "image/x-icon",
|
||||
Mime::Jpeg => "image/jpeg",
|
||||
Mime::Png => "image/png",
|
||||
Mime::Apng => "image/apng",
|
||||
Mime::Gif => "image/gif",
|
||||
Mime::Webp => "image/webp",
|
||||
Mime::Avif => "image/avif",
|
||||
Mime::Toml => "application/toml",
|
||||
Mime::Xml => "application/xml",
|
||||
Mime::Json => "application/json",
|
||||
Mime::Js => "text/javascript",
|
||||
Mime::Pdf => "application/pdf",
|
||||
Mime::Epub => "application/epub+zip",
|
||||
Mime::Unknown => "application/octet-stream",
|
||||
};
|
||||
String::from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mime {
|
||||
pub fn guess(path: &str) -> Mime {
|
||||
if let Some(pair) = path.rsplit_once('.') {
|
||||
Mime::from(pair.1)
|
||||
} else {
|
||||
Mime::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
mononoki/mononoki-latin-400-normal.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
reforma/Reforma1969-Blanca.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
reforma/Reforma1969-Gris.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
reforma/Reforma1969-GrisItalica.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
reforma/Reforma1969-BlancaItalica.woff2
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
maven/maven-pro-latin-400-normal.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
cormorant/cormorant-infant-latin-300-normal.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
cormorant/cormorant-infant-latin-300-italic.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
rawengulk/RawengulkLight.otf
|
||||
|
|
@ -589,7 +589,7 @@ text = """
|
|||
- [ ] Branch deeper
|
||||
- Customization
|
||||
- [ ] Custom assets (favicon, CSS)
|
||||
- [ ] Drop all hardcoded assets endpoints
|
||||
- [x] Drop all hardcoded assets endpoints
|
||||
- [ ] Custom header include
|
||||
- [ ] Custom templates
|
||||
- [ ] Themes
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
65
static/public/assets/fonts/fonts.css
Normal file
65
static/public/assets/fonts/fonts.css
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
@font-face {
|
||||
src: url("maven/maven-pro-latin-400-normal.woff2") format("woff2");
|
||||
font-family: "sans";
|
||||
size-adjust: 110%;
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src:
|
||||
url("cormorant/cormorant-infant-latin-300-normal.woff2")
|
||||
format("woff2");
|
||||
font-family: "serifed";
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src:
|
||||
url("cormorant/cormorant-infant-latin-300-italic.woff2")
|
||||
format("woff2");
|
||||
font-family: "serifed";
|
||||
font-style: italic;
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("mononoki/mononoki-latin-400-normal.woff2") format("woff2");
|
||||
font-family: "mono";
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("rawengulk/RawengulkLight.woff2") format("woff2");
|
||||
font-family: "title";
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("reforma/Reforma1969-Blanca.woff2") format("woff2");
|
||||
font-family: "prose";
|
||||
size-adjust: 120%;
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("reforma/Reforma1969-Gris.woff2") format("woff2");
|
||||
font-family: "prose";
|
||||
font-weight: bold;
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("reforma/Reforma1969-BlancaItalica.woff2") format("woff2");
|
||||
src: url("prose-italic");
|
||||
font-family: "prose";
|
||||
font-style: italic;
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("reforma/Reforma1969-GrisItalica.woff2") format("woff2");
|
||||
font-family: "prose";
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
display: swap;
|
||||
}
|
||||
BIN
static/public/assets/fonts/rawengulk/RawengulkLight.woff2
Normal file
BIN
static/public/assets/fonts/rawengulk/RawengulkLight.woff2
Normal file
Binary file not shown.
|
|
@ -2,64 +2,6 @@
|
|||
--base-font-size: 1em;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/sans");
|
||||
font-family: "sans";
|
||||
display: swap;
|
||||
size-adjust: 110%;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/serifed");
|
||||
font-family: "serifed";
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/serifed-italic");
|
||||
font-family: "serifed";
|
||||
display: swap;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/mono");
|
||||
font-family: "mono";
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/title");
|
||||
font-family: "title";
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/prose");
|
||||
font-family: "prose";
|
||||
display: swap;
|
||||
size-adjust: 120%;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/prose-bold");
|
||||
font-family: "prose";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/prose-italic");
|
||||
font-family: "prose";
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/prose-bold-italic");
|
||||
font-family: "prose";
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -366,9 +308,9 @@ em#index-node-count {
|
|||
}
|
||||
|
||||
table {
|
||||
margin: auto;
|
||||
border-collapse: collapse;
|
||||
border: 0.5px dotted #666;
|
||||
margin: auto;
|
||||
border-collapse: collapse;
|
||||
border: 0.5px dotted #666;
|
||||
}
|
||||
|
||||
td, th {
|
||||
|
|
@ -12,8 +12,9 @@
|
|||
{% endif %}
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/static/style.css" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link href="/static/assets/fonts/fonts.css" rel="stylesheet">
|
||||
<link href="/static/assets/style.css" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="/static/assets/favicon.svg">
|
||||
{% block head %}
|
||||
{% endblock head %}
|
||||
</head>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue