use super::page::{Page, PageTemplate}; use crate::app::AppConfig; use crate::static_files::StaticFilesManager; use serde::ser::{SerializeStruct, Serializer}; use serde::{Deserialize, Serialize}; use std::io::prelude::*; use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct WebSiteData { root_page: Page, #[serde(default = "Vec::new")] assets_index: Vec<String>, templates: Vec<PageTemplate>, } pub struct WebSiteBuilder(WebSiteData); #[derive(Debug, Clone)] pub struct WebSite { last_generated_page_id: usize, root_page: Page, pub static_files_manager: StaticFilesManager, templates: Vec<PageTemplate>, } impl Serialize for WebSite { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("WebSite", 3)?; state.serialize_field("root_page", &self.root_page)?; state.serialize_field("templates", &self.templates)?; state.serialize_field("assets_index", &self.static_files_manager.index)?; state.end() } } impl WebSiteBuilder { pub fn with_static_files_manager( &mut self, static_files_manager: StaticFilesManager, ) -> WebSite { WebSite { last_generated_page_id: 0, root_page: self.0.root_page.clone(), static_files_manager: { let mut static_files_manager = static_files_manager; static_files_manager.add_assets_pathes(&self.0.assets_index); static_files_manager }, templates: self.0.templates.clone(), } } pub fn from_json(json: &str) -> Self { WebSiteBuilder(serde_json::from_str(json).unwrap()) } #[cfg(test)] pub fn testing(test_dir: &PathBuf) -> WebSite { Self::from_json(crate::testing::TEST_JSON_WEBSITE) .with_static_files_manager(StaticFilesManager::testing_new(test_dir).unwrap()) } fn blank_website_template() -> PathBuf { std::env::current_dir() .unwrap() .join("templates") .join("new_website.json") } pub fn load(config: &AppConfig) -> Self { let file_path = match &config.load { Some(pth) => pth.clone(), None => { if let Some(loaded) = Self::try_load_from_existing_file(config) { return loaded; } Self::blank_website_template() } }; Self::from_json(&std::fs::read_to_string(file_path).unwrap()) } fn try_load_from_existing_file(config: &AppConfig) -> Option<Self> { let website_file_path = config.storage_dir.join("website.json"); if website_file_path.exists() { match &std::fs::read_to_string(&website_file_path) { Ok(json) => return Some(Self::from_json(&json)), Err(_) => return None, } } None } } impl WebSite { pub fn build(&mut self) -> Result<Self, String> { if let Err(err) = self.root_page.build( &self.templates, PathBuf::from("/"), &self.static_files_manager, ) { return Err(err); }; self.generate_pages_ids(); Ok(self.clone()) } pub fn get_all_pages_as_vec(&self) -> Vec<&Page> { let mut pages = Vec::new(); fn collect_pages<'a>(root: &'a Page, vec: &mut Vec<&'a Page>) { vec.push(root); for p in root.sub_pages.iter() { collect_pages(p, vec); } } collect_pages(&self.root_page, &mut pages); pages } fn max_page_id(&self) -> usize { self.get_all_pages_as_vec() .iter() .map(|p| p.get_id()) .max() .unwrap() } fn generate_pages_ids<'a>(&'a mut self) { self.last_generated_page_id = self.max_page_id(); fn process_page(page: &mut Page, id: &mut usize) { *id += 1; page.set_id(*id); for sp in page.sub_pages.iter_mut() { process_page(sp, id); } } process_page(&mut self.root_page, &mut self.last_generated_page_id); } pub fn save(&self, config: &AppConfig) -> std::io::Result<()> { let save_json = serde_json::to_string(self).unwrap(); let json_path = config.storage_dir.join("website.json"); let mut f = std::fs::OpenOptions::new() .write(true) .create(true) .truncate(true) .open(&json_path)?; f.write_all(save_json.as_bytes())?; f.flush()?; Ok(()) } fn find_page(page: &Page, match_id: usize) -> Option<&Page> { if page.get_id() == match_id { return Some(page); } for sp in page.sub_pages.iter() { if let Some(page) = Self::find_page(sp, match_id) { return Some(page); }; } None } fn find_page_mut(page: &mut Page, match_id: usize) -> Option<&mut Page> { if page.get_id() == match_id { return Some(page); } for sp in page.sub_pages.iter_mut() { if let Some(p) = Self::find_page_mut(sp, match_id) { return Some(p); }; } None } pub fn get_page(&self, id: usize) -> Option<&Page> { Self::find_page(&self.root_page, id) } pub fn get_page_mut(&mut self, id: usize) -> Option<&mut Page> { Self::find_page_mut(&mut self.root_page, id) } pub fn add_page(&mut self, parent_page_id: usize, page: Page) -> Result<(), String> { let id = self.last_generated_page_id + 1; let parent_page = Self::find_page_mut(&mut self.root_page, parent_page_id); match parent_page { Some(pp) => { let mut page = page; page.set_id(id); pp.add_sub_page(page); } None => { return Err(format!( "Parent page with id {} was not found", parent_page_id )) } } self.last_generated_page_id = id; Ok(()) } pub fn update_page(&mut self, id: usize, updated_page: Page) -> Result<&mut Page, String> { match self.get_page_mut(id) { Some(page) => { *page = updated_page; Ok(page) } None => Err(format!("Page with id {} was not found", id)), } } } #[cfg(test)] mod test_website { use super::*; fn test_website(pth: &PathBuf) -> WebSite { WebSiteBuilder::testing(pth).build().unwrap() } fn remove_test_dir(pth: &PathBuf) { let _ = std::fs::remove_dir_all(pth); } #[test] fn test_pages_id() { let test_dir = &PathBuf::from("./test"); let ws = test_website(&test_dir); assert_eq!(ws.last_generated_page_id, 3); assert_eq!(ws.max_page_id(), 3); assert!(!ws.get_all_pages_as_vec().iter().any(|p| p.get_id() == 0)); remove_test_dir(&test_dir); } #[test] fn test_get_page() { let test_dir = &PathBuf::from("./test"); let ws = test_website(&test_dir); let page = ws.get_page(1); assert!(page.is_some()); let page = ws.get_page(2); assert!(page.is_some()); let page = ws.get_page(3); assert!(page.is_some()); let page = ws.get_page(4); assert!(page.is_none()); remove_test_dir(&test_dir); } #[test] fn test_add_page() { let test_dir = &PathBuf::from("./test"); let mut ws = test_website(&test_dir); let test_new_page: &'static str = " { \"template_name\": \"TEST TEMPLATE\", \"metadata\": { \"title\": \"Added page\", \"description\": \"Added page descr\", \"image\": [\"https://example.com/images/ex_pic.png\"], \"css\": [], \"js\": [], \"url_slug\": \"\", \"lang\": \"en\" }, \"body\": [ { \"tag\": \"h1\", \"text\": \"Added page\" } ], \"sub_pages\": [] } "; let new_page: Page = serde_json::from_str(test_new_page).unwrap(); let added = ws.add_page(1, new_page); assert!(added.is_ok()); let retrieved = ws.get_page(4); assert!(retrieved.is_some()); let retrieved = retrieved.unwrap(); assert_eq!(retrieved.metadata.title, "Added page"); remove_test_dir(&test_dir); } #[test] fn test_update_page() { let test_dir = PathBuf::from("./test"); let mut ws = test_website(&test_dir); let mut root_page = ws.get_page_mut(1).unwrap().clone(); assert_eq!(root_page.metadata.title, "TEST"); root_page.metadata.title = String::from("UPDATED"); let updated = ws.update_page(1, root_page).unwrap(); assert_eq!(updated.metadata.title, "UPDATED"); let root_page = ws.get_page(1).unwrap(); assert_eq!(root_page.metadata.title, "UPDATED"); remove_test_dir(&test_dir); } }