Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
Commit 3a8686ef authored by Pierre Jarriges's avatar Pierre Jarriges
Browse files

pages served as html static real files

parent ee9597e8
No related branches found
No related tags found
No related merge requests found
...@@ -5,11 +5,6 @@ ...@@ -5,11 +5,6 @@
------------------------------------------------ ------------------------------------------------
_______ _______
Krustacea is a package that contains a web server written in Rust (Actix based) and a web application to build a static website. Krustacea is a package that contains a web server written in Rust (Actix based) and a web application to build a static website.
The web server loads a website as a single JSON document, the html contents is built in memory by the server at start, and the website is served without the need of any static html file. It also handles the necessary static files such as images, or additional source code like css or javascript.
Modifications to the website object will be done through the use of a REST API and the web application on client side.
_________________ _________________
## **/!\\** WIP ## ## **/!\\** WIP ##
...@@ -40,7 +35,7 @@ A website data is store as a JSON file with the following structure ...@@ -40,7 +35,7 @@ A website data is store as a JSON file with the following structure
"metadata": { "metadata": {
"title": "Hello Krustcea !", "title": "Hello Krustcea !",
"description": "An example website", "description": "An example website",
"image": "https://example.com/static/images/ex_pic.png", "image": "https://example.com/images/ex_pic.png",
"css": [], "css": [],
"js": [], "js": [],
"url_slug": "", "url_slug": "",
...@@ -109,11 +104,11 @@ A website data is store as a JSON file with the following structure ...@@ -109,11 +104,11 @@ A website data is store as a JSON file with the following structure
} }
], ],
"assets_index": [ "assets_index": [
"/static/images/toto.jpg", "/images/toto.jpg",
"/static/sounds/toto.mp3", "/sounds/toto.mp3",
"/static/video/toto.mp4", "/video/toto.mp4",
"/static/docs/toto.xcf", "/docs/toto.xcf",
"/static/source_code/toto.js" "/source_code/toto.js"
] ]
} }
``` ```
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"metadata": { "metadata": {
"title": "Hello Krustcea !", "title": "Hello Krustcea !",
"description": "An example website", "description": "An example website",
"image": "https://example.com/static/images/ex_pic.png", "image": "https://example.com/images/ex_pic.png",
"css": [], "css": [],
"js": [], "js": [],
"url_slug": "", "url_slug": "",
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
}, },
"templates": [ "templates": [
{ {
"name": "Custom template", "name": "A Custom template",
"layout": { "layout": {
"display": "grid" "display": "grid"
}, },
...@@ -73,10 +73,10 @@ ...@@ -73,10 +73,10 @@
} }
], ],
"assets_index": [ "assets_index": [
"/static/images/toto.jpg", "/images/toto.jpg",
"/static/sounds/toto.mp3", "/sounds/toto.mp3",
"/static/video/toto.mp4", "/video/toto.mp4",
"/static/docs/toto.xcf", "/docs/toto.xcf",
"/static/source_code/toto.js" "/source_code/toto.js"
] ]
} }
\ No newline at end of file
...@@ -32,7 +32,7 @@ async fn main() -> std::io::Result<()> { ...@@ -32,7 +32,7 @@ async fn main() -> std::io::Result<()> {
let port_tls = app_state.config.port_tls; let port_tls = app_state.config.port_tls;
let srv_conf = tls_config(&app_state.config); let srv_conf = tls_config(&app_state.config);
let static_dir = website.static_files_manager.dir.clone(); let dir = website.static_files_manager.dir.clone();
let mut_app_state = std::sync::Mutex::new(app_state); let mut_app_state = std::sync::Mutex::new(app_state);
let app_state = web::Data::new(mut_app_state); let app_state = web::Data::new(mut_app_state);
let mut_website = web::Data::new(std::sync::Mutex::new(website)); let mut_website = web::Data::new(std::sync::Mutex::new(website));
...@@ -44,7 +44,6 @@ async fn main() -> std::io::Result<()> { ...@@ -44,7 +44,6 @@ async fn main() -> std::io::Result<()> {
.wrap(RedirectHttps::default().to_port(port_tls)) .wrap(RedirectHttps::default().to_port(port_tls))
.app_data(web::Data::clone(&app_state)) .app_data(web::Data::clone(&app_state))
.app_data(web::Data::clone(&mut_website)) .app_data(web::Data::clone(&mut_website))
.service(Files::new("/static/", &static_dir))
.service( .service(
web::scope("/admin") web::scope("/admin")
.service(service::admin_login) .service(service::admin_login)
...@@ -56,7 +55,7 @@ async fn main() -> std::io::Result<()> { ...@@ -56,7 +55,7 @@ async fn main() -> std::io::Result<()> {
), ),
) )
.service(service::files::favicon) .service(service::files::favicon)
.service(service::page) .service(Files::new("/", &dir).index_file("index.html"))
}) })
.bind(format!("{}:{}", host, port))? .bind(format!("{}:{}", host, port))?
.bind_rustls(format!("{}:{}", host, port_tls), srv_conf)? .bind_rustls(format!("{}:{}", host, port_tls), srv_conf)?
......
...@@ -76,7 +76,7 @@ pub async fn admin_login() -> impl Responder { ...@@ -76,7 +76,7 @@ pub async fn admin_login() -> impl Responder {
<meta http-equiv='X-UA-Compatible' content='IE=edge'> <meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Krutacea - Admin Login</title> <title>Krutacea - Admin Login</title>
<link rel='stylesheet' href='/static/default/admin.css'> <link rel='stylesheet' href='/default/admin.css'>
</head> </head>
<body> <body>
...@@ -92,7 +92,7 @@ pub async fn admin_login() -> impl Responder { ...@@ -92,7 +92,7 @@ pub async fn admin_login() -> impl Responder {
<input type='submit' /> <input type='submit' />
</form> </form>
</body> </body>
<script src='/static/default/admin.js'></script> <script src='/default/admin.js'></script>
</html> </html>
", ",
) )
......
mod admin; mod admin;
pub mod files; pub mod files;
mod page;
pub use admin::*; pub use admin::*;
pub use page::*;
use crate::website::WebSite;
use actix_web::{get, web, HttpResponse, Responder};
use std::path::PathBuf;
#[get("/{pth:.*}")]
pub async fn page(
website: web::Data<std::sync::Mutex<WebSite>>,
pth: web::Path<PathBuf>,
) -> impl Responder {
let website = website.lock().unwrap();
let pth = pth.into_inner();
match website.get_page_by_url(&pth) {
Some(page) => HttpResponse::Ok().body(page.html.to_string()),
None => HttpResponse::NotFound().body(format!("Not found {}", pth.display())),
}
}
use crate::app::AppState; use crate::app::AppState;
use crate::website::Page;
use std::io::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
...@@ -18,7 +20,7 @@ const STATIC_ASSETS_DIRECTORIES: [&'static str; 6] = [ ...@@ -18,7 +20,7 @@ const STATIC_ASSETS_DIRECTORIES: [&'static str; 6] = [
impl StaticFilesManager { impl StaticFilesManager {
pub fn new(app_state: &AppState) -> Result<Self, String> { pub fn new(app_state: &AppState) -> Result<Self, String> {
match Self::create_dir_if_missing(&app_state.config.storage_dir) { match Self::create_dir_tree(&app_state.config.storage_dir) {
Ok(dir) => Ok(StaticFilesManager { Ok(dir) => Ok(StaticFilesManager {
index: Vec::new(), index: Vec::new(),
dir, dir,
...@@ -29,7 +31,7 @@ impl StaticFilesManager { ...@@ -29,7 +31,7 @@ impl StaticFilesManager {
#[cfg(test)] #[cfg(test)]
pub fn testing_new(test_dir: &PathBuf) -> Result<Self, String> { pub fn testing_new(test_dir: &PathBuf) -> Result<Self, String> {
match Self::create_dir_if_missing(test_dir) { match Self::create_dir_tree(test_dir) {
Ok(dir) => Ok(StaticFilesManager { Ok(dir) => Ok(StaticFilesManager {
index: Vec::new(), index: Vec::new(),
dir, dir,
...@@ -38,30 +40,30 @@ impl StaticFilesManager { ...@@ -38,30 +40,30 @@ impl StaticFilesManager {
} }
} }
fn create_dir_if_missing(app_dir: &PathBuf) -> Result<PathBuf, String> { fn create_dir_tree(dir: &PathBuf) -> Result<PathBuf, String> {
let static_dir = app_dir.join("static"); if !dir.exists() {
if let Err(err) = std::fs::create_dir_all(&dir) {
return Err(format!("{}", err));
};
}
if !static_dir.exists() { if let Err(err) = Self::create_assets_directories_structure(&dir) {
match std::fs::create_dir_all(&static_dir) { return Err(format!("{}", err));
Ok(_) => { };
if let Err(err) = Self::create_assets_directories_structure(&static_dir) {
return Err(format!("{}", err));
};
if let Err(err) = Self::copy_default_files(&static_dir) { if let Err(err) = Self::copy_default_files(&dir) {
return Err(format!("{}", err)); return Err(format!("{}", err));
}
}
Err(err) => return Err(format!("{}", err)),
}
} }
Ok(static_dir) Ok(dir.clone())
} }
fn create_assets_directories_structure(root: &PathBuf) -> Result<(), std::io::Error> { fn create_assets_directories_structure(root: &PathBuf) -> Result<(), std::io::Error> {
for d in STATIC_ASSETS_DIRECTORIES { for d in STATIC_ASSETS_DIRECTORIES {
std::fs::create_dir(root.join(d))?; let p = root.join(d);
if !p.exists() {
std::fs::create_dir(p)?;
}
} }
Ok(()) Ok(())
...@@ -72,6 +74,8 @@ impl StaticFilesManager { ...@@ -72,6 +74,8 @@ impl StaticFilesManager {
let default_static = static_dir.join("default"); let default_static = static_dir.join("default");
let mut cpy_options = fs_extra::dir::CopyOptions::new(); let mut cpy_options = fs_extra::dir::CopyOptions::new();
cpy_options.content_only = true; cpy_options.content_only = true;
cpy_options.overwrite = true;
match fs_extra::dir::copy(local_default_static, default_static, &cpy_options) { match fs_extra::dir::copy(local_default_static, default_static, &cpy_options) {
Err(err) => Err(format!("{}", err)), Err(err) => Err(format!("{}", err)),
Ok(_) => Ok(()), Ok(_) => Ok(()),
...@@ -142,6 +146,25 @@ impl StaticFilesManager { ...@@ -142,6 +146,25 @@ impl StaticFilesManager {
pub fn get_index(&self) -> Vec<String> { pub fn get_index(&self) -> Vec<String> {
self.index.clone() self.index.clone()
} }
pub fn write_html_page(&self, url: &PathBuf, page: &Page) -> std::io::Result<()> {
let dir = self.dir.join(self.clean_relative_path(url));
if !dir.exists() {
std::fs::create_dir_all(&dir)?;
}
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&dir.join("index.html"))?;
file.write_all(page.html.to_string().as_bytes())?;
file.flush()?;
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]
...@@ -164,7 +187,7 @@ mod test_static_files_manager { ...@@ -164,7 +187,7 @@ mod test_static_files_manager {
let _manager = StaticFilesManager::testing_new(&test_dir).unwrap(); let _manager = StaticFilesManager::testing_new(&test_dir).unwrap();
for d in STATIC_ASSETS_DIRECTORIES { for d in STATIC_ASSETS_DIRECTORIES {
let p = test_dir.join("static").join(d); let p = test_dir.join(d);
let exists = p.exists(); let exists = p.exists();
assert!( assert!(
exists, exists,
...@@ -181,7 +204,7 @@ mod test_static_files_manager { ...@@ -181,7 +204,7 @@ mod test_static_files_manager {
fn test_indexation() { fn test_indexation() {
let test_dir = create_test_dir(); let test_dir = create_test_dir();
let mut manager = StaticFilesManager::testing_new(&test_dir).unwrap(); let mut manager = StaticFilesManager::testing_new(&test_dir).unwrap();
let file_pth = test_dir.join("static").join("docs").join("testing.txt"); let file_pth = test_dir.join("docs").join("testing.txt");
std::fs::File::create(&file_pth).unwrap(); std::fs::File::create(&file_pth).unwrap();
manager = manager.build(); manager = manager.build();
...@@ -195,10 +218,10 @@ mod test_static_files_manager { ...@@ -195,10 +218,10 @@ mod test_static_files_manager {
} }
#[test] #[test]
fn test_pushd_andremove_path() { fn test_pushd_and_remove_path() {
let test_dir = create_test_dir(); let test_dir = create_test_dir();
let mut manager = StaticFilesManager::testing_new(&test_dir).unwrap().build(); let mut manager = StaticFilesManager::testing_new(&test_dir).unwrap().build();
let file_pth = test_dir.join("static").join("docs").join("testing.txt"); let file_pth = test_dir.join("docs").join("testing.txt");
std::fs::File::create(&file_pth).unwrap(); std::fs::File::create(&file_pth).unwrap();
let indexed_path = Path::new("docs/testing.txt"); let indexed_path = Path::new("docs/testing.txt");
let added = manager.push_path(&indexed_path); let added = manager.push_path(&indexed_path);
......
#[cfg(test)] #[cfg(test)]
pub const TEST_JSON_WEBSITE: &'static str = " pub const _TEST_JSON_WEBSITE: &'static str = "
{ {
\"root_page\": { \"root_page\": {
\"template_name\": \"TEST TEMPLATE\", \"template_name\": \"TEST TEMPLATE\",
\"metadata\": { \"metadata\": {
\"title\": \"TEST\", \"title\": \"TEST\",
\"description\": \"TEST DESCRIPTION\", \"description\": \"TEST DESCRIPTION\",
\"image\": [\"https://test/static/images/test.png\"], \"image\": [\"https://test/images/test.png\"],
\"css\": [], \"css\": [],
\"lang\":\"en\", \"lang\":\"en\",
\"js\": [], \"js\": [],
...@@ -24,7 +24,7 @@ pub const TEST_JSON_WEBSITE: &'static str = " ...@@ -24,7 +24,7 @@ pub const TEST_JSON_WEBSITE: &'static str = "
\"metadata\": { \"metadata\": {
\"title\": \"TEST SUBPAGE\", \"title\": \"TEST SUBPAGE\",
\"description\": \"TEST DESCRIPTION SUBPAGE\", \"description\": \"TEST DESCRIPTION SUBPAGE\",
\"image\": [\"https://test/static/images/test.png\"], \"image\": [\"https://test/images/test.png\"],
\"css\": [], \"css\": [],
\"lang\":\"en\", \"lang\":\"en\",
\"js\": [], \"js\": [],
...@@ -42,7 +42,7 @@ pub const TEST_JSON_WEBSITE: &'static str = " ...@@ -42,7 +42,7 @@ pub const TEST_JSON_WEBSITE: &'static str = "
\"metadata\": { \"metadata\": {
\"title\": \"TEST NESTED\", \"title\": \"TEST NESTED\",
\"description\": \"TEST DESCRIPTION NESTED\", \"description\": \"TEST DESCRIPTION NESTED\",
\"image\": [\"https://test/static/images/test.png\"], \"image\": [\"https://test/images/test.png\"],
\"css\": [], \"css\": [],
\"js\": [], \"js\": [],
\"url_slug\": \"nested\", \"url_slug\": \"nested\",
......
...@@ -3,4 +3,5 @@ mod html; ...@@ -3,4 +3,5 @@ mod html;
mod page; mod page;
mod website; mod website;
pub use page::Page;
pub use website::*; pub use website::*;
...@@ -3,8 +3,10 @@ use super::html::{ ...@@ -3,8 +3,10 @@ use super::html::{
replace_placeholders, HtmlDoc, HtmlElement, CSS_LINK_FRAGMENT, FAVICON_LINK_FRAGMENT, replace_placeholders, HtmlDoc, HtmlElement, CSS_LINK_FRAGMENT, FAVICON_LINK_FRAGMENT,
IMAGE_LINKS_FRAGMENT, SCRIPT_FRAGMENT, IMAGE_LINKS_FRAGMENT, SCRIPT_FRAGMENT,
}; };
use crate::StaticFilesManager;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Page { pub struct Page {
...@@ -84,7 +86,7 @@ pub struct FaviconLink(String); ...@@ -84,7 +86,7 @@ pub struct FaviconLink(String);
impl FaviconLink { impl FaviconLink {
pub fn new() -> Self { pub fn new() -> Self {
FaviconLink(String::from("/static/default/favicon.ico")) FaviconLink(String::from("/default/favicon.ico"))
} }
} }
...@@ -107,7 +109,7 @@ pub struct CSSLinks(Vec<String>); ...@@ -107,7 +109,7 @@ pub struct CSSLinks(Vec<String>);
impl CSSLinks { impl CSSLinks {
pub fn new() -> Self { pub fn new() -> Self {
CSSLinks(vec!["/static/default/style.css".to_owned()]) CSSLinks(vec!["/default/style.css".to_owned()])
} }
} }
...@@ -135,7 +137,7 @@ pub struct JSLinks(Vec<String>); ...@@ -135,7 +137,7 @@ pub struct JSLinks(Vec<String>);
impl JSLinks { impl JSLinks {
pub fn new() -> Self { pub fn new() -> Self {
JSLinks(vec!["/static/default/script.js".to_owned()]) JSLinks(vec!["/default/script.js".to_owned()])
} }
} }
...@@ -187,6 +189,31 @@ impl std::fmt::Display for ImageLinks { ...@@ -187,6 +189,31 @@ impl std::fmt::Display for ImageLinks {
} }
impl Page { impl Page {
pub fn build(
&mut self,
templates: &Vec<PageTemplate>,
from_url: PathBuf,
static_files_manager: &StaticFilesManager,
) {
self.build_with_template(
templates
.iter()
.find(|t| t.name == self.template_name)
.expect("Page template not found")
.clone(),
);
self.build_html();
let url = from_url.join(&self.metadata.url_slug);
static_files_manager.write_html_page(&url, self).unwrap();
for p in self.sub_pages.iter_mut() {
p.build(templates, url.clone(), static_files_manager);
}
}
pub fn build_html(&mut self) { pub fn build_html(&mut self) {
self.html = HtmlDoc::from_page(self); self.html = HtmlDoc::from_page(self);
} }
...@@ -233,18 +260,18 @@ mod test_pages { ...@@ -233,18 +260,18 @@ mod test_pages {
description: String::from("test descr"), description: String::from("test descr"),
url_slug: String::from("test-page"), url_slug: String::from("test-page"),
css: CSSLinks(vec![ css: CSSLinks(vec![
"/static/source_code/mystyle.css".to_string(), "/source_code/mystyle.css".to_string(),
"/static/source_code/mystyle2.css".to_string(), "/source_code/mystyle2.css".to_string(),
]), ]),
js: JSLinks(vec![ js: JSLinks(vec![
"/static/source_code/myscript.js".to_string(), "/source_code/myscript.js".to_string(),
"/static/source_code/myscript2.js".to_string(), "/source_code/myscript2.js".to_string(),
]), ]),
favicon: FaviconLink(String::from("/static/images/testicon.ico")), favicon: FaviconLink(String::from("/images/testicon.ico")),
author: String::from("test author"), author: String::from("test author"),
image: ImageLinks(vec![ image: ImageLinks(vec![
"/static/images/testimage.png".to_string(), "/images/testimage.png".to_string(),
"/static/images/testimage2.png".to_string(), "/images/testimage2.png".to_string(),
]), ]),
} }
} }
...@@ -334,7 +361,7 @@ mod test_pages { ...@@ -334,7 +361,7 @@ mod test_pages {
let pmd = test_page_metadata(); let pmd = test_page_metadata();
assert_eq!( assert_eq!(
pmd.favicon.to_string(), pmd.favicon.to_string(),
"<link rel='icon' type='image/*' href='/static/images/testicon.ico'/>" "<link rel='icon' type='image/*' href='/images/testicon.ico'/>"
) )
} }
...@@ -343,8 +370,8 @@ mod test_pages { ...@@ -343,8 +370,8 @@ mod test_pages {
let pmd = test_page_metadata(); let pmd = test_page_metadata();
assert_eq!( assert_eq!(
pmd.css.to_string(), pmd.css.to_string(),
"<link rel='stylesheet' href='/static/source_code/mystyle.css'> "<link rel='stylesheet' href='/source_code/mystyle.css'>
<link rel='stylesheet' href='/static/source_code/mystyle2.css'>" <link rel='stylesheet' href='/source_code/mystyle2.css'>"
) )
} }
...@@ -353,8 +380,8 @@ mod test_pages { ...@@ -353,8 +380,8 @@ mod test_pages {
let pmd = test_page_metadata(); let pmd = test_page_metadata();
assert_eq!( assert_eq!(
pmd.js.to_string(), pmd.js.to_string(),
"<script src='/static/source_code/myscript.js'></script> "<script src='/source_code/myscript.js'></script>
<script src='/static/source_code/myscript2.js'></script>" <script src='/source_code/myscript2.js'></script>"
) )
} }
...@@ -363,12 +390,12 @@ mod test_pages { ...@@ -363,12 +390,12 @@ mod test_pages {
let pmd = test_page_metadata(); let pmd = test_page_metadata();
assert_eq!( assert_eq!(
pmd.image.to_string(), pmd.image.to_string(),
"<meta name='image' content='/static/images/testimage.png'/> "<meta name='image' content='/images/testimage.png'/>
<meta property='og:image' content='/static/images/testimage.png'/> <meta property='og:image' content='/images/testimage.png'/>
<meta property='twitter:image' content='/static/images/testimage.png'/> <meta property='twitter:image' content='/images/testimage.png'/>
<meta name='image' content='/static/images/testimage2.png'/> <meta name='image' content='/images/testimage2.png'/>
<meta property='og:image' content='/static/images/testimage2.png'/> <meta property='og:image' content='/images/testimage2.png'/>
<meta property='twitter:image' content='/static/images/testimage2.png'/>" <meta property='twitter:image' content='/images/testimage2.png'/>"
) )
} }
......
...@@ -2,7 +2,6 @@ use super::page::{Page, PageTemplate}; ...@@ -2,7 +2,6 @@ use super::page::{Page, PageTemplate};
use crate::app::AppConfig; use crate::app::AppConfig;
use crate::static_files::StaticFilesManager; use crate::static_files::StaticFilesManager;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
...@@ -18,7 +17,6 @@ pub struct WebSite { ...@@ -18,7 +17,6 @@ pub struct WebSite {
root_page: Page, root_page: Page,
pub static_files_manager: StaticFilesManager, pub static_files_manager: StaticFilesManager,
templates: Vec<PageTemplate>, templates: Vec<PageTemplate>,
pages_index: HashMap<PathBuf, Page>,
} }
impl WebSiteBuilder { impl WebSiteBuilder {
...@@ -38,7 +36,6 @@ impl WebSiteBuilder { ...@@ -38,7 +36,6 @@ impl WebSiteBuilder {
static_files_manager static_files_manager
}, },
templates: self.templates.clone(), templates: self.templates.clone(),
pages_index: HashMap::new(),
} }
} }
...@@ -57,70 +54,11 @@ impl WebSiteBuilder { ...@@ -57,70 +54,11 @@ impl WebSiteBuilder {
impl WebSite { impl WebSite {
pub fn build(&mut self) -> Self { pub fn build(&mut self) -> Self {
self.root_page.build_with_template( self.root_page.build(
self.templates &self.templates,
.iter() PathBuf::from("/"),
.find(|t| t.name == self.root_page.template_name) &self.static_files_manager,
.expect("Page template not found")
.clone(),
); );
self.root_page.build_html();
for p in self.root_page.sub_pages.iter_mut() {
p.build_with_template(
self.templates
.iter()
.find(|t| t.name == p.template_name)
.expect("Page template not found")
.clone(),
);
p.build_html();
}
self.build_pages_index(self.root_page.clone(), PathBuf::from("/"));
self.clone() self.clone()
} }
fn build_pages_index(&mut self, root_page: Page, from_url: PathBuf) {
let url = from_url.join(&root_page.metadata.url_slug);
self.pages_index.insert(url.clone(), root_page.clone());
for p in root_page.sub_pages {
self.build_pages_index(p, url.clone());
}
}
pub fn get_page_by_url(&self, url: &PathBuf) -> Option<&Page> {
self.pages_index.get(&PathBuf::from("/").join(url))
}
}
#[cfg(test)]
mod test_website {
use super::*;
use crate::testing::TEST_JSON_WEBSITE;
#[test]
fn test_index_pages_by_slug() {
let website = WebSiteBuilder::from_json(TEST_JSON_WEBSITE)
.with_static_files_manager(StaticFilesManager {
dir: PathBuf::from("."),
index: Vec::new(),
})
.build();
let root_page = website.get_page_by_url(&PathBuf::from("/"));
assert!(root_page.is_some());
assert_eq!(root_page.unwrap().metadata.title, "TEST");
let sub_page = website.get_page_by_url(&PathBuf::from("subpage"));
assert!(sub_page.is_some());
assert_eq!(sub_page.unwrap().metadata.title, "TEST SUBPAGE");
let nested_page = website.get_page_by_url(&PathBuf::from("subpage/nested"));
assert!(nested_page.is_some());
assert_eq!(nested_page.unwrap().metadata.title, "TEST NESTED");
}
} }
...@@ -12,6 +12,55 @@ ...@@ -12,6 +12,55 @@
"tag": "h1", "tag": "h1",
"text": "New website" "text": "New website"
} }
],
"sub_pages": [
{
"template_name": "Nav Content Footer",
"metadata": {
"title": "A sub page",
"description": "A new subpage",
"url_slug": "subpage",
"lang": "en"
},
"body": [
{
"tag": "h1",
"text": "subpage"
}
],
"sub_pages": [
{
"template_name": "Nav Content Footer",
"metadata": {
"title": "A nested page",
"description": "A new nested page",
"url_slug": "nested",
"lang": "en"
},
"body": [
{
"tag": "h1",
"text": "nested"
}
]
}
]
},
{
"template_name": "Nav Content Footer",
"metadata": {
"title": "Another sub page",
"description": "A new subpage",
"url_slug": "other",
"lang": "en"
},
"body": [
{
"tag": "h1",
"text": "other"
}
]
}
] ]
}, },
"templates": [ "templates": [
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment