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 @@
------------------------------------------------
_______
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 ##
......@@ -40,7 +35,7 @@ A website data is store as a JSON file with the following structure
"metadata": {
"title": "Hello Krustcea !",
"description": "An example website",
"image": "https://example.com/static/images/ex_pic.png",
"image": "https://example.com/images/ex_pic.png",
"css": [],
"js": [],
"url_slug": "",
......@@ -109,11 +104,11 @@ A website data is store as a JSON file with the following structure
}
],
"assets_index": [
"/static/images/toto.jpg",
"/static/sounds/toto.mp3",
"/static/video/toto.mp4",
"/static/docs/toto.xcf",
"/static/source_code/toto.js"
"/images/toto.jpg",
"/sounds/toto.mp3",
"/video/toto.mp4",
"/docs/toto.xcf",
"/source_code/toto.js"
]
}
```
\ No newline at end of file
......@@ -4,7 +4,7 @@
"metadata": {
"title": "Hello Krustcea !",
"description": "An example website",
"image": "https://example.com/static/images/ex_pic.png",
"image": "https://example.com/images/ex_pic.png",
"css": [],
"js": [],
"url_slug": "",
......@@ -33,7 +33,7 @@
},
"templates": [
{
"name": "Custom template",
"name": "A Custom template",
"layout": {
"display": "grid"
},
......@@ -73,10 +73,10 @@
}
],
"assets_index": [
"/static/images/toto.jpg",
"/static/sounds/toto.mp3",
"/static/video/toto.mp4",
"/static/docs/toto.xcf",
"/static/source_code/toto.js"
"/images/toto.jpg",
"/sounds/toto.mp3",
"/video/toto.mp4",
"/docs/toto.xcf",
"/source_code/toto.js"
]
}
\ No newline at end of file
......@@ -32,7 +32,7 @@ async fn main() -> std::io::Result<()> {
let port_tls = app_state.config.port_tls;
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 app_state = web::Data::new(mut_app_state);
let mut_website = web::Data::new(std::sync::Mutex::new(website));
......@@ -44,7 +44,6 @@ async fn main() -> std::io::Result<()> {
.wrap(RedirectHttps::default().to_port(port_tls))
.app_data(web::Data::clone(&app_state))
.app_data(web::Data::clone(&mut_website))
.service(Files::new("/static/", &static_dir))
.service(
web::scope("/admin")
.service(service::admin_login)
......@@ -56,7 +55,7 @@ async fn main() -> std::io::Result<()> {
),
)
.service(service::files::favicon)
.service(service::page)
.service(Files::new("/", &dir).index_file("index.html"))
})
.bind(format!("{}:{}", host, port))?
.bind_rustls(format!("{}:{}", host, port_tls), srv_conf)?
......
......@@ -76,7 +76,7 @@ pub async fn admin_login() -> impl Responder {
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Krutacea - Admin Login</title>
<link rel='stylesheet' href='/static/default/admin.css'>
<link rel='stylesheet' href='/default/admin.css'>
</head>
<body>
......@@ -92,7 +92,7 @@ pub async fn admin_login() -> impl Responder {
<input type='submit' />
</form>
</body>
<script src='/static/default/admin.js'></script>
<script src='/default/admin.js'></script>
</html>
",
)
......
mod admin;
pub mod files;
mod page;
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::website::Page;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug)]
......@@ -18,7 +20,7 @@ const STATIC_ASSETS_DIRECTORIES: [&'static str; 6] = [
impl StaticFilesManager {
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 {
index: Vec::new(),
dir,
......@@ -29,7 +31,7 @@ impl StaticFilesManager {
#[cfg(test)]
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 {
index: Vec::new(),
dir,
......@@ -38,30 +40,30 @@ impl StaticFilesManager {
}
}
fn create_dir_if_missing(app_dir: &PathBuf) -> Result<PathBuf, String> {
let static_dir = app_dir.join("static");
fn create_dir_tree(dir: &PathBuf) -> Result<PathBuf, String> {
if !dir.exists() {
if let Err(err) = std::fs::create_dir_all(&dir) {
return Err(format!("{}", err));
};
}
if !static_dir.exists() {
match std::fs::create_dir_all(&static_dir) {
Ok(_) => {
if let Err(err) = Self::create_assets_directories_structure(&static_dir) {
return Err(format!("{}", err));
};
if let Err(err) = Self::create_assets_directories_structure(&dir) {
return Err(format!("{}", err));
};
if let Err(err) = Self::copy_default_files(&static_dir) {
return Err(format!("{}", err));
}
}
Err(err) => return Err(format!("{}", err)),
}
if let Err(err) = Self::copy_default_files(&dir) {
return Err(format!("{}", err));
}
Ok(static_dir)
Ok(dir.clone())
}
fn create_assets_directories_structure(root: &PathBuf) -> Result<(), std::io::Error> {
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(())
......@@ -72,6 +74,8 @@ impl StaticFilesManager {
let default_static = static_dir.join("default");
let mut cpy_options = fs_extra::dir::CopyOptions::new();
cpy_options.content_only = true;
cpy_options.overwrite = true;
match fs_extra::dir::copy(local_default_static, default_static, &cpy_options) {
Err(err) => Err(format!("{}", err)),
Ok(_) => Ok(()),
......@@ -142,6 +146,25 @@ impl StaticFilesManager {
pub fn get_index(&self) -> Vec<String> {
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)]
......@@ -164,7 +187,7 @@ mod test_static_files_manager {
let _manager = StaticFilesManager::testing_new(&test_dir).unwrap();
for d in STATIC_ASSETS_DIRECTORIES {
let p = test_dir.join("static").join(d);
let p = test_dir.join(d);
let exists = p.exists();
assert!(
exists,
......@@ -181,7 +204,7 @@ mod test_static_files_manager {
fn test_indexation() {
let test_dir = create_test_dir();
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();
manager = manager.build();
......@@ -195,10 +218,10 @@ mod test_static_files_manager {
}
#[test]
fn test_pushd_andremove_path() {
fn test_pushd_and_remove_path() {
let test_dir = create_test_dir();
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();
let indexed_path = Path::new("docs/testing.txt");
let added = manager.push_path(&indexed_path);
......
#[cfg(test)]
pub const TEST_JSON_WEBSITE: &'static str = "
pub const _TEST_JSON_WEBSITE: &'static str = "
{
\"root_page\": {
\"template_name\": \"TEST TEMPLATE\",
\"metadata\": {
\"title\": \"TEST\",
\"description\": \"TEST DESCRIPTION\",
\"image\": [\"https://test/static/images/test.png\"],
\"image\": [\"https://test/images/test.png\"],
\"css\": [],
\"lang\":\"en\",
\"js\": [],
......@@ -24,7 +24,7 @@ pub const TEST_JSON_WEBSITE: &'static str = "
\"metadata\": {
\"title\": \"TEST SUBPAGE\",
\"description\": \"TEST DESCRIPTION SUBPAGE\",
\"image\": [\"https://test/static/images/test.png\"],
\"image\": [\"https://test/images/test.png\"],
\"css\": [],
\"lang\":\"en\",
\"js\": [],
......@@ -42,7 +42,7 @@ pub const TEST_JSON_WEBSITE: &'static str = "
\"metadata\": {
\"title\": \"TEST NESTED\",
\"description\": \"TEST DESCRIPTION NESTED\",
\"image\": [\"https://test/static/images/test.png\"],
\"image\": [\"https://test/images/test.png\"],
\"css\": [],
\"js\": [],
\"url_slug\": \"nested\",
......
......@@ -3,4 +3,5 @@ mod html;
mod page;
mod website;
pub use page::Page;
pub use website::*;
......@@ -3,8 +3,10 @@ use super::html::{
replace_placeholders, HtmlDoc, HtmlElement, CSS_LINK_FRAGMENT, FAVICON_LINK_FRAGMENT,
IMAGE_LINKS_FRAGMENT, SCRIPT_FRAGMENT,
};
use crate::StaticFilesManager;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Page {
......@@ -84,7 +86,7 @@ pub struct FaviconLink(String);
impl FaviconLink {
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>);
impl CSSLinks {
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>);
impl JSLinks {
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 {
}
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) {
self.html = HtmlDoc::from_page(self);
}
......@@ -233,18 +260,18 @@ mod test_pages {
description: String::from("test descr"),
url_slug: String::from("test-page"),
css: CSSLinks(vec![
"/static/source_code/mystyle.css".to_string(),
"/static/source_code/mystyle2.css".to_string(),
"/source_code/mystyle.css".to_string(),
"/source_code/mystyle2.css".to_string(),
]),
js: JSLinks(vec![
"/static/source_code/myscript.js".to_string(),
"/static/source_code/myscript2.js".to_string(),
"/source_code/myscript.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"),
image: ImageLinks(vec![
"/static/images/testimage.png".to_string(),
"/static/images/testimage2.png".to_string(),
"/images/testimage.png".to_string(),
"/images/testimage2.png".to_string(),
]),
}
}
......@@ -334,7 +361,7 @@ mod test_pages {
let pmd = test_page_metadata();
assert_eq!(
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 {
let pmd = test_page_metadata();
assert_eq!(
pmd.css.to_string(),
"<link rel='stylesheet' href='/static/source_code/mystyle.css'>
<link rel='stylesheet' href='/static/source_code/mystyle2.css'>"
"<link rel='stylesheet' href='/source_code/mystyle.css'>
<link rel='stylesheet' href='/source_code/mystyle2.css'>"
)
}
......@@ -353,8 +380,8 @@ mod test_pages {
let pmd = test_page_metadata();
assert_eq!(
pmd.js.to_string(),
"<script src='/static/source_code/myscript.js'></script>
<script src='/static/source_code/myscript2.js'></script>"
"<script src='/source_code/myscript.js'></script>
<script src='/source_code/myscript2.js'></script>"
)
}
......@@ -363,12 +390,12 @@ mod test_pages {
let pmd = test_page_metadata();
assert_eq!(
pmd.image.to_string(),
"<meta name='image' content='/static/images/testimage.png'/>
<meta property='og:image' content='/static/images/testimage.png'/>
<meta property='twitter:image' content='/static/images/testimage.png'/>
<meta name='image' content='/static/images/testimage2.png'/>
<meta property='og:image' content='/static/images/testimage2.png'/>
<meta property='twitter:image' content='/static/images/testimage2.png'/>"
"<meta name='image' content='/images/testimage.png'/>
<meta property='og:image' content='/images/testimage.png'/>
<meta property='twitter:image' content='/images/testimage.png'/>
<meta name='image' content='/images/testimage2.png'/>
<meta property='og:image' content='/images/testimage2.png'/>
<meta property='twitter:image' content='/images/testimage2.png'/>"
)
}
......
......@@ -2,7 +2,6 @@ use super::page::{Page, PageTemplate};
use crate::app::AppConfig;
use crate::static_files::StaticFilesManager;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, Clone)]
......@@ -18,7 +17,6 @@ pub struct WebSite {
root_page: Page,
pub static_files_manager: StaticFilesManager,
templates: Vec<PageTemplate>,
pages_index: HashMap<PathBuf, Page>,
}
impl WebSiteBuilder {
......@@ -38,7 +36,6 @@ impl WebSiteBuilder {
static_files_manager
},
templates: self.templates.clone(),
pages_index: HashMap::new(),
}
}
......@@ -57,70 +54,11 @@ impl WebSiteBuilder {
impl WebSite {
pub fn build(&mut self) -> Self {
self.root_page.build_with_template(
self.templates
.iter()
.find(|t| t.name == self.root_page.template_name)
.expect("Page template not found")
.clone(),
self.root_page.build(
&self.templates,
PathBuf::from("/"),
&self.static_files_manager,
);
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()
}
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 @@
"tag": "h1",
"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": [
......
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