diff --git a/Cargo.lock b/Cargo.lock index 24761b6ffe6fa423e1fd860613da52a14304bf39..69056ae02c9c0db20f9cfb2f946a3939a1f882a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,6 +377,8 @@ dependencies = [ "actix-web", "rustls", "rustls-pemfile", + "serde", + "serde_json", ] [[package]] @@ -1019,9 +1021,23 @@ checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" [[package]] name = "serde" -version = "1.0.142" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" diff --git a/Cargo.toml b/Cargo.toml index eb87a3d53529fbf61825e46f0d77b614b623ab98..ac24c85b8db633b2c03f9454b40eb18d31c23106 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,5 @@ edition = "2021" actix-web = { version = "4.1.0", features = ["rustls", "secure-cookies"] } rustls = "0.20.6" rustls-pemfile = "1.0.1" +serde = { version = "1.0.143", features = ["derive"] } +serde_json = "1.0.83" diff --git a/src/main.rs b/src/main.rs index 70790a55dd7cb34c6e1bfded610580afd26d5db7..8e35f653d56ebcaa91cee86b74c3add202cab04b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,26 @@ +mod website; use actix_web::{get, App, HttpResponse, HttpServer, Responder}; +use website::WebSite; + +fn load_website_template() -> WebSite { + let site_template_path = std::env::current_dir() + .unwrap() + .join("templates") + .join("new_website.json"); + let site_template = std::fs::read_to_string(site_template_path).unwrap(); + WebSite::new(serde_json::from_str(&site_template).unwrap()) +} #[get("/{pth:.*}")] -async fn page(pth: actix_web::web::Path<String>) -> impl Responder { - HttpResponse::Ok().body(format!("Page path {}", pth)) +async fn page( + website: actix_web::web::Data<WebSite>, + pth: actix_web::web::Path<String>, +) -> impl Responder { + let pth = format!("/{}/", pth.into_inner()); // BUG Use trailing slash middleware ? or match root or not root + match website.get_page_by_url(&pth) { + Some(page) => HttpResponse::Ok().body(page.html_doc.clone()), + None => HttpResponse::NotFound().body(format!("Not found {}", pth)), + } } #[get("/admin/dashboard")] @@ -17,15 +35,16 @@ async fn admin_login() -> impl Responder { #[actix_web::main] async fn main() -> std::io::Result<()> { + let website = load_website_template(); // GET HOST AND CERTS DIR FROM CLI ARGUMENT // Get port from arg, or get context from arg and define default port, or set default port to standard - // LOAD A WEBSITE SCHEMA (JSON) FROM CLI ARGUMENT PATH OR CREATE A NEW ONE + // LOAD A WEBSITE SCHEMA (JSON) FROM CLI ARGUMENT PATH OR search in /var/{sitename} and load it CREATE A NEW ONE // create pages resources with templates and the contents from the json file // Save the resources in an appstate - // create the static dir - // create the static files index (like Arc<Mutex<Index>>) + // create the static dir in standard location if doesn't exist (like /var/{sitename}/static) + // create the static files index (like Arc<Mutex<StaticFilesIndex>>) // create a Rest service at root with extensive path argument: like #[get(/{pth:.*})] // Then parse the website document and return the corresponding template, or 404 template @@ -51,9 +70,11 @@ async fn main() -> std::io::Result<()> { .with_single_cert(vec![cert], key) .expect("bad certificate/key"); - HttpServer::new(|| { + HttpServer::new(move || { App::new() .wrap(actix_web::middleware::Logger::default()) + .wrap(actix_web::middleware::Compress::default()) + .app_data(actix_web::web::Data::new(website.clone())) .service(admin_dashboard) .service(admin_login) .service(page) diff --git a/src/website.rs b/src/website.rs new file mode 100644 index 0000000000000000000000000000000000000000..648e010f4d4e92083faca4bbb76cf26baaf52154 --- /dev/null +++ b/src/website.rs @@ -0,0 +1,124 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PageData { + pub title: String, + pub lang: String, + pub description: String, + pub slug: String, + pub html_body: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WebPage { + pub page_data: PageData, + pub html_doc: String, +} + +impl PageData { + pub fn to_web_page(&self) -> WebPage { + WebPage { + page_data: self.clone(), + html_doc: self.html_body.clone(), // TMP + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PagesTree { + pub page_data: PageData, + pub sub_pages: Option<Vec<PagesTree>>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WebSite { + pages_tree: PagesTree, + pages_index_by_url: HashMap<String, WebPage>, +} + +impl WebSite { + pub fn new(pages_tree: PagesTree) -> Self { + let mut pages_index_by_url = HashMap::new(); + WebSite::create_index_by_url(&mut pages_index_by_url, &pages_tree, String::new()); + + WebSite { + pages_tree, + pages_index_by_url, + } + } + + fn create_index_by_url( + index: &mut HashMap<String, WebPage>, + pages_tree: &PagesTree, + from_url: String, + ) { + let page_data = pages_tree.page_data.clone(); + let url = format!("{}{}/", from_url, page_data.slug); + + index.insert(url.to_owned(), page_data.to_web_page()); + + if let Some(sub_pages) = &pages_tree.sub_pages { + for pt in sub_pages { + WebSite::create_index_by_url(index, pt, url.to_owned()); + } + } + } + + pub fn get_page_by_url(&self, url: &String) -> Option<&WebPage> { + self.pages_index_by_url.get(url) + } +} + +#[cfg(test)] +mod test_website { + use super::*; + const JSON_TEMPLATE: &'static str = " +{ + \"page_data\": { + \"title\": \"Test Website\", + \"slug\": \"\", + \"lang\": \"en\", + \"description\": \"A test website\", + \"html_body\": \"<h1>Test Website</h1>\" + }, + \"sub_pages\": [ + { + \"page_data\": { + \"title\": \"A sub page\", + \"slug\": \"subpage\", + \"lang\": \"en\", + \"description\": \"A sub page of the testing web site\", + \"html_body\": \"<h1>A sub page</h1>\" + }, + \"sub_pages\": [ + { + \"page_data\": { + \"title\": \"Another page\", + \"lang\": \"en\", + \"slug\": \"otherpage\", + \"description\": \"Another testing page\", + \"html_body\": \"<h1>Another page</h1>\" + } + } + ] + } + ] +} + "; + + #[test] + fn test_index_pages_by_slug() { + let pages_tree: PagesTree = serde_json::from_str(JSON_TEMPLATE).unwrap(); + let website = WebSite::new(pages_tree); + let root_page = website.get_page_by_url(&"/".to_string()); + assert!(root_page.is_some()); + let root_page = root_page.unwrap(); + assert_eq!(root_page.page_data.html_body, "<h1>Test Website</h1>"); + + let sub_page = website.get_page_by_url(&"/subpage/".to_string()); + assert!(sub_page.is_some()); + let sub_page = sub_page.unwrap(); + assert_eq!(sub_page.page_data.html_body, "<h1>A sub page</h1>"); + } +} diff --git a/templates/new_website.json b/templates/new_website.json new file mode 100644 index 0000000000000000000000000000000000000000..92a78fa0bb711d3ca85824202be08689a336ee56 --- /dev/null +++ b/templates/new_website.json @@ -0,0 +1,40 @@ +{ + "page_data": { + "title": "New Website", + "lang": "en", + "slug": "", + "description": "A new website", + "html_body": "<h1>New Website</h1>" + }, + "sub_pages": [ + { + "page_data": { + "title": "A sub page", + "lang": "en", + "slug": "subpage", + "description": "A sub page of the new web site", + "html_body": "<h1>A sub page</h1>" + } + }, + { + "page_data": { + "title": "Some other page", + "lang": "en", + "slug": "otherpage", + "description": "Some other page of the new web site", + "html_body": "<h1>Another page</h1>" + }, + "sub_pages": [ + { + "page_data": { + "title": "A sub page of the other page", + "lang": "en", + "slug": "othersubpage", + "description": "A sub page of the other page of the new web site", + "html_body": "<h1>A subpage</h1>" + } + } + ] + } + ] +} \ No newline at end of file