Merge pull request 'add an index page and add name functions to the API and cache busting' (#7) from index-page into main

Reviewed-on: #7
This commit is contained in:
mossfet 2023-07-11 11:08:07 +00:00
commit 50d92e998a
18 changed files with 682 additions and 174 deletions

111
Cargo.lock generated
View file

@ -17,6 +17,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.3.2" version = "0.3.2"
@ -443,6 +452,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
"percent-encoding",
]
[[package]] [[package]]
name = "fsevent-sys" name = "fsevent-sys"
version = "4.1.0" version = "4.1.0"
@ -596,6 +614,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.9" version = "0.2.9"
@ -653,6 +677,16 @@ dependencies = [
"want", "want",
] ]
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@ -816,6 +850,12 @@ dependencies = [
"regex-automata", "regex-automata",
] ]
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -824,13 +864,17 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "meowy-webring" name = "meowy-webring"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"askama", "askama",
"askama_rocket", "askama_rocket",
"hex",
"log", "log",
"md5",
"notify", "notify",
"proc_macros",
"rocket", "rocket",
"rocket_cors",
"rust-embed", "rust-embed",
"serde", "serde",
"serde_json", "serde_json",
@ -1067,6 +1111,14 @@ dependencies = [
"yansi", "yansi",
] ]
[[package]]
name = "proc_macros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.28" version = "1.0.28"
@ -1161,6 +1213,8 @@ version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
dependencies = [ dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.2", "regex-syntax 0.7.2",
] ]
@ -1240,6 +1294,20 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "rocket_cors"
version = "0.6.0-alpha2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12771b47f52e34d5d0e0e444aeba382863e73263cb9e18847e7d5b74aa2cbd0"
dependencies = [
"http",
"log",
"regex",
"rocket",
"unicase",
"url",
]
[[package]] [[package]]
name = "rocket_http" name = "rocket_http"
version = "0.5.0-rc.3" version = "0.5.0-rc.3"
@ -1579,6 +1647,21 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.29.0" version = "1.29.0"
@ -1775,18 +1858,44 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.9" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.4" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "url"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.1"

View file

@ -1,20 +1,27 @@
[workspace] [workspace]
members = ["cli", "shared"] members = ["cli", "shared", "proc-macros"]
[package] [package]
name = "meowy-webring" name = "meowy-webring"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
rust-version = "1.70" rust-version = "1.70"
[profile.dev]
lto = false
[profile.release]
lto = "thin"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
hex = "0.4"
log = "0.4" log = "0.4"
md5 = "0.7"
[dependencies.rocket] [dependencies.rocket]
version = "=0.5.0-rc.3" version = "=0.5.0-rc.3"
default_features = false default-features = false
features = ["json"] features = ["json"]
[dependencies.rust-embed] [dependencies.rust-embed]
@ -51,3 +58,10 @@ default-features = false
version = "6" version = "6"
default-features = false default-features = false
features = ["macos_fsevent"] features = ["macos_fsevent"]
[dependencies.rocket_cors]
version = "=0.6.0-alpha2"
default_features = false
[dependencies.proc_macros]
path = "./proc-macros"

64
LICENSE
View file

@ -2,14 +2,14 @@ Creative Commons Legal Code
CC0 1.0 Universal CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER. HEREUNDER.
Statement of Purpose Statement of Purpose
@ -43,22 +43,22 @@ Related Rights"). Copyright and Related Rights include, but are not
limited to, the following: limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display, i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work; communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s); ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work; likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work, iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below; subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data v. rights protecting the extraction, dissemination, use and reuse of data
in a Work; in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation protection of databases, and under any national implementation
thereof, including any amended or successor version of such thereof, including any amended or successor version of such
directive); and directive); and
vii. other similar, equivalent or corresponding rights throughout the vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national world based on applicable law or treaty, and any national
implementations thereof. implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention 2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently, of, applicable law, Affirmer hereby overtly, fully, permanently,
@ -102,20 +102,20 @@ express Statement of Purpose.
4. Limitations and Disclaimers. 4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned, a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document. surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied, warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law. the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work. limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the consents, permissions or other rights required for any use of the
Work. Work.
d. Affirmer understands and acknowledges that Creative Commons is not a d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to party to this document and has no duty or obligation with respect to
this CC0 or use of the Work. this CC0 or use of the Work.

13
proc-macros/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "proc_macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
syn = "2.0"
quote = "1.0"

35
proc-macros/src/lib.rs Normal file
View file

@ -0,0 +1,35 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse::Parser, parse_macro_input, DeriveInput};
#[proc_macro_attribute]
pub fn uses_base_template(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(item as DeriveInput);
let base_template_field = syn::Field::parse_named
.parse2(
quote! {
pub base_template: BaseTemplate
}
.into(),
)
.unwrap();
match &mut input.data {
syn::Data::Struct(ref mut struct_data) => {
match &mut struct_data.fields {
syn::Fields::Named(fields) => {
fields.named.push(base_template_field);
}
_ => (),
}
return quote! {
#input
}
.into();
}
_ => {
panic!("bad")
}
};
}

43
public/hyperlegible.css Normal file
View file

@ -0,0 +1,43 @@
@font-face {
font-family: Atkinson Hyperlegible;
font-style: normal;
font-display: swap;
font-weight: 400;
src: url(/public/woff2/atkinson-hyperlegible-latin-ext-400-normal.woff2) format("woff2"),
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
unicode-range: U+0100-024F,
U+0259,
U+1E00-1EFF,
U+2020,
U+20A0-20AB,
U+20AD-20CF,
U+2113,
U+2C60-2C7F,
U+A720-A7FF
}
@font-face {
font-family: Atkinson Hyperlegible;
font-style: normal;
font-display: swap;
font-weight: 400;
src: url(/public/woff2/atkinson-hyperlegible-latin-400-normal.woff2) format("woff2"),
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
unicode-range: U+0000-00FF,
U+0131,
U+0152-0153,
U+02BB-02BC,
U+02C6,
U+02DA,
U+02DC,
U+2000-206F,
U+2074,
U+20AC,
U+2122,
U+2191,
U+2193,
U+2212,
U+2215,
U+FEFF,
U+FFFD
}

View file

@ -14,59 +14,51 @@
} }
} }
:root {
--h1-font-size: 3.225rem;
--h2-font-size: 2.825rem;
--h3-font-size: 2.225rem;
--h4-font-size: 1.665rem;
--default-font-size: 1.375rem;
--h6-font-size: 1.185rem;
}
body { body {
font-family: "Atkinson Hyperlegible", sans-serif; font-family: "Atkinson Hyperlegible", sans-serif;
text-align: center; text-align: center;
font-size: var(--default-font-size);
max-width: 600px; max-width: 600px;
margin: auto; margin: auto;
background-color: var(--background-color); background-color: var(--background-color);
color: var(--text-color) color: var(--text-color)
} }
p {
font-size: 22px; a {
font-size: var(--default-font-size);
color: var(--link-color);
} }
@font-face { h1 {
font-family: Atkinson Hyperlegible; font-size: var(--h1-font-size);
font-style: normal;
font-display: swap;
font-weight: 400;
src: url(/public/woff2/atkinson-hyperlegible-latin-ext-400-normal.woff2) format("woff2"),
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
unicode-range: U+0100-024F,
U+0259,
U+1E00-1EFF,
U+2020,
U+20A0-20AB,
U+20AD-20CF,
U+2113,
U+2C60-2C7F,
U+A720-A7FF
} }
@font-face { h2 {
font-family: Atkinson Hyperlegible; font-size: var(--h2-font-size);
font-style: normal; }
font-display: swap;
font-weight: 400; h3 {
src: url(/public/woff2/atkinson-hyperlegible-latin-400-normal.woff2) format("woff2"), font-size: var(--h3-font-size);
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff"); }
unicode-range: U+0000-00FF,
U+0131, h4 {
U+0152-0153, font-size: var(--h4-font-size);
U+02BB-02BC, }
U+02C6,
U+02DA, h5 {
U+02DC, font-size: var(--default-font-size);
U+2000-206F, }
U+2074,
U+20AC, h6 {
U+2122, font-size: var(--h6-font-size);
U+2191,
U+2193,
U+2212,
U+2215,
U+FEFF,
U+FFFD
} }

View file

@ -1,63 +0,0 @@
use std::borrow::Cow;
use askama_rocket::Template;
use rocket::http::Status;
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "public/"]
pub struct PublicAssets;
#[derive(Responder)]
#[response(status = 200, content_type = "font/woff2")]
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
#[derive(Responder)]
#[response(status = 200, content_type = "font/woff")]
pub struct RawWoffFont(pub Cow<'static, [u8]>);
#[derive(Template)]
#[template(path = "error.html")]
pub struct ErrorTemplate<'a> {
pub error: &'a str,
pub error_description: &'a str
}
#[derive(Responder)]
pub struct ErrorTemplateResponder<'a> {
template: ErrorTemplate<'a>
}
#[get("/style.css")]
pub fn style() -> Result<rocket::response::content::RawCss<String>, Status> {
let style = PublicAssets::get("style.css").unwrap();
match std::str::from_utf8(&style.data) {
Ok(style) => Ok(rocket::response::content::RawCss::<String>(style.to_string())),
Err(_) => Err(Status::InternalServerError),
}
}
#[get("/woff2/<font>")]
pub fn woff2_font(font: &str) -> Result<RawWoff2Font, Status> {
let latin = "atkinson-hyperlegible-latin-400-normal.woff2";
let latin_ext = "atkinson-hyperlegible-latin-ext-400-normal.woff2";
if font == latin {
Ok(RawWoff2Font(PublicAssets::get(latin).unwrap().data))
} else if font == latin_ext {
Ok(RawWoff2Font(PublicAssets::get(latin_ext).unwrap().data))
} else {
Err(Status::NotFound)
}
}
#[get("/woff/<font>")]
pub fn woff_font(font: &str) -> Result<RawWoffFont, Status> {
let all = "atkinson-hyperlegible-all-400-normal.woff";
if font == all {
Ok(RawWoffFont(PublicAssets::get(all).unwrap().data))
} else {
Err(Status::NotFound)
}
}

96
src/assets/files.rs Normal file
View file

@ -0,0 +1,96 @@
use rocket::http::Status;
use rust_embed::RustEmbed;
use std::{borrow::Cow, sync::OnceLock};
#[derive(RustEmbed)]
#[folder = "public/"]
struct Assets;
#[derive(Debug)]
pub struct Files {
pub atkinson_latin_woff2: BinaryFile,
pub atkinson_latin_ext_woff2: BinaryFile,
pub atkinson_all_woff: BinaryFile,
pub style: TextFile,
pub hyperlegible: TextFile,
}
#[derive(Debug, Clone)]
pub struct FileMetadata {
pub filename: String,
pub extension: String,
pub hash: String,
}
impl FileMetadata {
pub fn get_hash_filename(&self) -> String {
let mut hash = self.hash.clone();
hash.truncate(8);
format!("{}.{}.{}", self.filename, hash, self.extension)
}
}
#[derive(Debug)]
pub struct BinaryFile {
pub data: Cow<'static, [u8]>,
pub metadata: FileMetadata,
}
#[derive(Debug, Clone)]
pub struct TextFile {
pub text: String,
pub metadata: FileMetadata,
}
pub static FILES: OnceLock<Files> = OnceLock::new();
pub fn get_file_wrapper() -> Result<&'static Files, Status> {
match FILES.get() {
Some(files) => Ok(files),
None => Err(Status::InternalServerError),
}
}
fn get_binary_file(filename: &str, extension: &str) -> Option<BinaryFile> {
match Assets::get(&format!("{}.{}", filename, extension)) {
Some(file) => {
let metadata = FileMetadata {
filename: filename.into(),
extension: extension.into(),
hash: hex::encode(file.metadata.sha256_hash()),
};
Some(BinaryFile {
data: file.data,
metadata: metadata,
})
}
None => None,
}
}
fn get_text_file(filename: &str, extension: &str) -> Option<TextFile> {
let file = get_binary_file(filename, extension)?;
match std::str::from_utf8(&file.data) {
Ok(string) => Some(TextFile {
text: string.into(),
metadata: file.metadata,
}),
Err(_) => None,
}
}
pub fn initialize_files() {
let files = Files {
atkinson_latin_woff2: get_binary_file("atkinson-hyperlegible-latin-400-normal", "woff2")
.unwrap(),
atkinson_latin_ext_woff2: get_binary_file(
"atkinson-hyperlegible-latin-ext-400-normal",
"woff2",
)
.unwrap(),
atkinson_all_woff: get_binary_file("atkinson-hyperlegible-all-400-normal", "woff").unwrap(),
style: get_text_file("style", "css").unwrap(),
hyperlegible: get_text_file("hyperlegible", "css").unwrap(),
};
FILES.set(files).unwrap();
}

7
src/assets/mod.rs Normal file
View file

@ -0,0 +1,7 @@
pub mod files;
mod routes;
pub mod templates;
pub use routes::style;
pub use routes::woff2_font;
pub use routes::woff_font;

96
src/assets/routes.rs Normal file
View file

@ -0,0 +1,96 @@
use super::{files::get_file_wrapper, templates::ErrorTemplate};
use rocket::{
http::{Header, Status},
response::{self, content::RawCss, Responder},
Response,
};
use std::borrow::Cow;
#[derive(Responder)]
#[response(status = 200, content_type = "font/woff2")]
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
#[derive(Responder)]
#[response(status = 200, content_type = "font/woff")]
pub struct RawWoffFont(pub Cow<'static, [u8]>);
#[derive(Responder)]
pub struct ErrorTemplateResponder<'a> {
template: ErrorTemplate<'a>,
}
pub struct CachedResponse<T> {
inner: T,
}
impl<'r, T> Responder<'r, 'static> for CachedResponse<T>
where
T: Responder<'r, 'static>,
{
fn respond_to(self, request: &'r rocket::Request<'_>) -> response::Result<'static> {
Response::build_from(self.inner.respond_to(request)?)
.header(Header::new("Cache-Control", "max-age=31536000, immutable"))
.ok()
}
}
impl<'r, T> From<T> for CachedResponse<T>
where
T: Responder<'r, 'static>,
{
fn from(value: T) -> Self {
CachedResponse { inner: value }
}
}
#[get("/css/<style>")]
pub fn style(style: &str) -> Result<CachedResponse<RawCss<String>>, Status> {
let style_file = &get_file_wrapper()?.style;
let hyperlegible_file = &get_file_wrapper()?.hyperlegible;
let style_name = style_file.metadata.get_hash_filename();
let hyperlegible_name = hyperlegible_file.metadata.get_hash_filename();
if style == style_name {
Ok(CachedResponse::from(RawCss::<String>(
style_file.text.clone(),
)))
} else if style == hyperlegible_name {
Ok(CachedResponse::from(RawCss::<String>(
hyperlegible_file.text.clone(),
)))
} else {
Err(Status::NotFound)
}
}
#[get("/woff2/<font>")]
pub fn woff2_font(font: &str) -> Result<CachedResponse<RawWoff2Font>, Status> {
let latin_file = &get_file_wrapper()?.atkinson_latin_woff2;
let latin_ext_file = &get_file_wrapper()?.atkinson_latin_ext_woff2;
let latin = latin_file.metadata.get_hash_filename();
let latin_ext = latin_file.metadata.get_hash_filename();
if font == latin {
Ok(CachedResponse::from(RawWoff2Font(latin_file.data.clone())))
} else if font == latin_ext {
Ok(CachedResponse::from(RawWoff2Font(
latin_ext_file.data.clone(),
)))
} else {
Err(Status::NotFound)
}
}
#[get("/woff/<font>")]
pub fn woff_font(font: &str) -> Result<CachedResponse<RawWoffFont>, Status> {
let all_file = &get_file_wrapper()?.atkinson_all_woff;
let all = all_file.metadata.get_hash_filename();
if font == all {
Ok(CachedResponse::from(RawWoffFont(all_file.data.clone())))
} else {
Err(Status::NotFound)
}
}

25
src/assets/templates.rs Normal file
View file

@ -0,0 +1,25 @@
use askama::Template;
use proc_macros::uses_base_template;
use shared::names::Site;
pub struct BaseTemplate {
pub style_filename: String,
pub atkinson_latin_woff2_filename: String,
pub atkinson_latin_ext_woff2_filename: String,
pub atkinson_all_woff_filename: String,
}
#[derive(Template)]
#[template(path = "error.html")]
#[uses_base_template]
pub struct ErrorTemplate<'a> {
pub error: &'a str,
pub error_description: &'a str,
}
#[derive(Template)]
#[template(path = "index.html")]
#[uses_base_template]
pub struct IndexTemplate {
pub sites: Vec<Site>,
}

View file

@ -17,4 +17,30 @@ pub fn next_url(source_url: &String, names: &Vec<Site>) -> Option<String> {
} }
} }
// TODO: previous_name, next_name pub fn previous_name(source_url: &String, names: &Vec<Site>) -> Option<String> {
match names.iter().position(|r| &r.url == source_url) {
Some(index) if index == 0 => match &names[names.len() - 1].name {
Some(name) => Some(name.clone()),
None => previous_url(source_url, names),
},
Some(index) => match &names[index - 1].name {
Some(name) => Some(name.clone()),
None => previous_url(source_url, names),
},
None => None,
}
}
pub fn next_name(source_url: &String, names: &Vec<Site>) -> Option<String> {
match names.iter().position(|r| &r.url == source_url) {
Some(index) if index == names.len() - 1 => match &names[0].name {
Some(name) => Some(name.clone()),
None => next_url(source_url, names),
},
Some(index) => match &names[index + 1].name {
Some(name) => Some(name.clone()),
None => next_url(source_url, names),
},
None => None,
}
}

View file

@ -1,4 +1,5 @@
use crate::watcher::hot_reloading; use crate::watcher::hot_reloading;
use assets::files::initialize_files;
use rocket::tokio; use rocket::tokio;
use sites::init_names; use sites::init_names;
@ -14,9 +15,12 @@ mod watcher;
#[launch] #[launch]
async fn rocket() -> _ { async fn rocket() -> _ {
init_names().unwrap(); init_names().unwrap();
initialize_files();
let cors = rocket_cors::CorsOptions::default().to_cors().unwrap();
tokio::task::spawn_blocking(hot_reloading); tokio::task::spawn_blocking(hot_reloading);
rocket::build() rocket::build()
.manage(cors)
.mount( .mount(
"/", "/",
routes![routes::index, routes::previous, routes::next, routes::name], routes![routes::index, routes::previous, routes::next, routes::name],

View file

@ -1,19 +1,50 @@
use crate::{ use crate::{
assets::ErrorTemplate, assets::{
links::{next_url, previous_url}, files::{get_file_wrapper, FileMetadata},
templates::{BaseTemplate, ErrorTemplate, IndexTemplate},
},
links::{next_name, next_url, previous_name, previous_url},
sites::get_global_names, sites::get_global_names,
}; };
use rocket::{ use rocket::{
http::Status, http::Status,
response::Redirect, response::Redirect,
serde::{json::Json, Serialize}, serde::{json::Json, Serialize},
}; };
use rocket_cors::{Guard, Responder};
const NOT_FOUND_ERROR: ErrorTemplate = ErrorTemplate { fn get_hash_filename(metadata: &FileMetadata) -> Result<String, Status> {
error: "Not Found", Ok(metadata.get_hash_filename())
error_description: "this URL could not be found on the webring.", }
};
fn get_base_template() -> Result<BaseTemplate, Status> {
let files = get_file_wrapper()?;
let style_filename = get_hash_filename(&files.style.metadata)?;
let atkinson_latin_woff2_filename = get_hash_filename(&files.atkinson_latin_woff2.metadata)?;
let atkinson_latin_ext_woff2_filename =
get_hash_filename(&files.atkinson_latin_ext_woff2.metadata)?;
let atkinson_all_woff_filename = get_hash_filename(&files.atkinson_all_woff.metadata)?;
let template = BaseTemplate {
style_filename,
atkinson_latin_woff2_filename,
atkinson_latin_ext_woff2_filename,
atkinson_all_woff_filename,
};
Ok(template)
}
fn not_found_error() -> Result<ErrorTemplate<'static>, Status> {
let base_template = get_base_template()?;
let template = ErrorTemplate {
error: "Not Found",
error_description: "this URL could not be found on the webring.",
base_template,
};
Ok(template)
}
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
@ -23,8 +54,13 @@ pub struct JsonResponse {
} }
#[get("/")] #[get("/")]
pub fn index() -> &'static str { pub async fn index() -> Result<IndexTemplate, Status> {
"Like, this is a webring, meow!" let base_template = get_base_template()?;
let template = IndexTemplate {
sites: get_global_names().await,
base_template,
};
Ok(template)
} }
#[get("/previous?<source_url>")] #[get("/previous?<source_url>")]
@ -44,21 +80,25 @@ pub async fn next(source_url: String) -> Result<Redirect, Status> {
} }
#[get("/name?<source_url>")] #[get("/name?<source_url>")]
pub async fn name(source_url: String) -> Result<Json<JsonResponse>, Status> { pub async fn name(
let previous_site_name = previous_url(&source_url, &get_global_names().await); source_url: String,
let next_site_name = next_url(&source_url, &get_global_names().await); cors: Guard<'_>,
) -> Responder<Result<Json<JsonResponse>, Status>> {
let previous_site_name = previous_name(&source_url, &get_global_names().await);
let next_site_name = next_name(&source_url, &get_global_names().await);
if previous_site_name.is_none() && next_site_name.is_none() { if previous_site_name.is_none() && next_site_name.is_none() {
return Err(Status::NotFound); return cors.responder(Err(Status::NotFound));
} }
Ok(Json(JsonResponse { let response = Ok(Json(JsonResponse {
previous_site_name, previous_site_name,
next_site_name, next_site_name,
})) }));
cors.responder(response)
} }
#[catch(404)] #[catch(404)]
pub fn not_found() -> ErrorTemplate<'static> { pub fn not_found() -> Result<ErrorTemplate<'static>, Status> {
NOT_FOUND_ERROR not_found_error()
} }

60
templates/base.html Normal file
View file

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Meowy Webring{% block title %}{% endblock %}</title>
{% block head %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<style>
@font-face {
font-family: Atkinson Hyperlegible;
font-style: normal;
font-display: swap;
font-weight: 400;
src: url("/public/woff2/{{ base_template.atkinson_latin_ext_woff2_filename }}") format("woff2"),
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
unicode-range: U+0100-024F,
U+0259,
U+1E00-1EFF,
U+2020,
U+20A0-20AB,
U+20AD-20CF,
U+2113,
U+2C60-2C7F,
U+A720-A7FF
}
@font-face {
font-family: Atkinson Hyperlegible;
font-style: normal;
font-display: swap;
font-weight: 400;
src: url("/public/woff2/{{ base_template.atkinson_latin_woff2_filename }}") format("woff2"),
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
unicode-range: U+0000-00FF,
U+0131,
U+0152-0153,
U+02BB-02BC,
U+02C6,
U+02DA,
U+02DC,
U+2000-206F,
U+2074,
U+20AC,
U+2122,
U+2191,
U+2193,
U+2212,
U+2215,
U+FEFF,
U+FFFD
}
</style>
<link rel="stylesheet" href="/public/css/{{ base_template.style_filename }}" />
{% endblock %}
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>

View file

@ -1,15 +1,10 @@
<!DOCTYPE html> {% extends "base.html" %}
<html lang="en">
<head> {% block title %} - {{ error }}{% endblock %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width"> {% block content %}
<title>Meowy Webring - {{ error }}</title> <main>
<link rel="stylesheet" href="/public/style.css" /> <h1>{{ error }}</h1>
</head> <p>{{ error_description }}</p>
<body> </main>
<main> {% endblock %}
<h1>{{ error }}</h1>
<p>{{ error_description }}</p>
</main>
</body>
</html>

16
templates/index.html Normal file
View file

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<main>
<h1>Meowy Webring</h1>
<h2>Sites</h2>
{% for site in sites %}
{% match site.name %}
{% when Some with (value) %}
<p><a href="https://{{ site.url }}">{{ value }}</a></p>
{% when None %}
<p><a href="https://{{ site.url }}">{{ site.url }}</a></p>
{% endmatch %}
{% endfor %}
</main>
{% endblock %}