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
website.rs 15.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • use super::page::{Page, PageTemplate};
    
    peterrabbit's avatar
    peterrabbit committed
    use crate::app::AppConfig;
    
    use crate::static_files::StaticFilesManager;
    
    use serde::ser::{SerializeStruct, Serializer};
    
    peterrabbit's avatar
    peterrabbit committed
    use serde::{Deserialize, Serialize};
    
    use std::io::prelude::*;
    
    peterrabbit's avatar
    peterrabbit committed
    use std::path::PathBuf;
    
    peterrabbit's avatar
    peterrabbit committed
    
    #[derive(Debug, Serialize, Deserialize, Clone)]
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    pub struct WebSiteData {
    
        root_page: Page,
        #[serde(default = "Vec::new")]
        assets_index: Vec<String>,
        templates: Vec<PageTemplate>,
    }
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    pub struct WebSiteBuilder(WebSiteData);
    
    
    peterrabbit's avatar
    peterrabbit committed
    pub struct WebSite {
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        last_generated_page_id: usize,
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        pub root_page: Page,
    
        pub static_files_manager: StaticFilesManager,
    
    peterrabbit's avatar
    peterrabbit committed
        templates: Vec<PageTemplate>,
    
    peterrabbit's avatar
    peterrabbit committed
    }
    
    
    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 {
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                last_generated_page_id: 0,
                root_page: self.0.root_page.clone(),
    
                static_files_manager: {
                    let mut static_files_manager = static_files_manager;
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                    static_files_manager.add_assets_pathes(&self.0.assets_index);
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                templates: self.0.templates.clone(),
    
    peterrabbit's avatar
    peterrabbit committed
            }
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        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;
                    }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
    
                    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
        }
    
    peterrabbit's avatar
    peterrabbit committed
    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);
            };
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            self.generate_pages_ids();
    
    
            Ok(self.clone())
    
    peterrabbit's avatar
    peterrabbit committed
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        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);
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                }
            }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            collect_pages(&self.root_page, &mut pages);
            pages
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        }
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        fn max_page_id(&self) -> usize {
            self.get_all_pages_as_vec()
                .iter()
                .map(|p| p.get_id())
                .max()
                .unwrap()
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        fn generate_pages_ids<'a>(&'a mut self) {
            self.last_generated_page_id = self.max_page_id();
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            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);
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                }
            }
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            process_page(&mut self.root_page, &mut self.last_generated_page_id);
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        }
    
    
        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(())
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
        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
        }
    
    
        fn find_page_parent_mut<'a>(
            page: &'a mut Page,
            parent_page: Option<&'a mut Page>,
            match_id: usize,
        ) -> Result<&'a mut Page, String> {
            if page.get_id() == match_id {
                return Ok(parent_page.expect("Page must have a parent"));
            }
    
            for sp in page.sub_pages.iter_mut() {
                if let Ok(parent) = Self::find_page_parent_mut(sp, Some(page), match_id) {
                    return Ok(parent);
                };
            }
    
            Err(format!("Page with id {} was not found", match_id))
        }
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        pub fn get_page(&self, id: usize) -> Option<&Page> {
            Self::find_page(&self.root_page, id)
        }
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        pub fn get_page_mut(&mut self, id: usize) -> Option<&mut Page> {
            Self::find_page_mut(&mut self.root_page, id)
        }
    
    
        fn get_page_parent_mut(&mut self, id: usize) -> Result<&mut Page, String> {
            Self::find_page_parent_mut(&mut self.root_page, None, id)
        }
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        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(())
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
        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)),
            }
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
        fn validate_add_template(&self, template: &PageTemplate) -> Result<(), String> {
            if !self.get_template(&template.name).is_none() {
                return Err(format!(
                    "A template named \"{}\" already exists",
                    template.name
                ));
            }
    
            if PageTemplate::get_page_body_placeholder(&mut template.clone().contents).is_err() {
                return Err(format!(
                    "Page body container was not found in template named \"{}\"",
                    template.name
                ));
            }
    
            Ok(())
        }
    
        fn validate_update_template(&self, template: &PageTemplate) -> Result<(), String> {
            if self.get_template(&template.name).is_none() {
                return Err(format!(
                    "Templated named \"{}\" was not found.",
                    template.name
                ));
            }
    
            if PageTemplate::get_page_body_placeholder(&mut template.clone().contents).is_err() {
                return Err(format!(
                    "Page body container was not found in template named \"{}\"",
                    template.name
                ));
            }
    
            Ok(())
        }
    
        fn get_template(&self, name: &String) -> Option<&PageTemplate> {
            self.templates.iter().find(|t| t.name.eq(name))
        }
    
        fn get_template_mut(&mut self, name: &String) -> Option<&mut PageTemplate> {
            self.templates.iter_mut().find(|t| t.name.eq(name))
        }
    
        pub fn add_template(&mut self, new_template: PageTemplate) -> Result<&PageTemplate, String> {
            match self.validate_add_template(&new_template) {
                Ok(()) => {
                    let name = new_template.name.to_owned();
                    self.templates.push(new_template);
                    Ok(self.get_template(&name).unwrap())
                }
                Err(msg) => Err(msg),
            }
        }
    
        pub fn update_template(
            &mut self,
            updated_template: PageTemplate,
        ) -> Result<&PageTemplate, String> {
            match self.validate_update_template(&updated_template) {
                Ok(()) => {
                    let template = self.get_template_mut(&updated_template.name).unwrap();
                    *template = updated_template;
    
                    Ok(template)
                }
                Err(msg) => Err(msg),
            }
        }
    
        pub fn update_page_rec_after_template_update(page: &mut Page, template: &PageTemplate) {
            page.update_template_if_same_name(template);
            for sp in page.sub_pages.iter_mut() {
                Self::update_page_rec_after_template_update(sp, template);
            }
        }
    
    
        fn validate_page_to_remove(&mut self, id: usize) -> Result<&mut Page, String> {
            let res = self.get_page_parent_mut(id);
            if let Err(msg) = res {
                return Err(msg);
            }
    
            Ok(res.unwrap())
        }
    
        pub fn remove_page(&mut self, id: usize) -> Result<(), String> {
            match self.validate_page_to_remove(id) {
                Ok(parent) => {
                    parent.sub_pages = parent
                        .sub_pages
                        .iter()
                        .filter(|sp| sp.get_id() != id)
                        .map(|p| p.clone())
                        .collect::<Vec<Page>>();
                    Ok(())
                }
                Err(msg) => Err(msg),
            }
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    }
    
    #[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);
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
        #[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);
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
        #[test]
        fn add_template() {
            let test_template: &'static str = "
    {
        \"name\": \"TEST ADD TEMPLATE\",
        \"layout\": {},
        \"contents\": [
            {
                \"tag\": \"div\",
                \"attrs\": {
                    \"class\": \"page-template\"
                },
                \"contents\": [
                    {
                        \"tag\": \"header\",
                        \"contents\": [
                            {
                                \"tag\": \"nav\"
                            }
                        ]
                    },
                    {
                        \"tag\": \"div\",
                        \"attrs\": {
                            \"id\": \"page-body\"
                        }
                    },
                    {
                        \"tag\": \"footer\"
                    }
                ]
            }
        ]
    }
            ";
    
            let test_dir = PathBuf::from("./test");
            let mut ws = test_website(&test_dir);
    
            let new_template: PageTemplate = serde_json::from_str(test_template).unwrap();
    
            let add_template_res = ws.add_template(new_template);
            assert!(add_template_res.is_ok());
    
            let added_template = add_template_res.unwrap();
            assert_eq!(added_template.name, "TEST ADD TEMPLATE");
    
            remove_test_dir(&test_dir);
        }
    
        #[test]
        fn test_update_template() {
            let test_dir = PathBuf::from("./test");
            let ws = test_website(&test_dir);
            let (updated, mut ws) = {
                let mut ws = ws;
                let mut template = ws
                    .get_template(&"TEST TEMPLATE".to_string())
                    .unwrap()
                    .clone();
    
                template.contents[0].tag = "changed_tag".to_owned();
    
                let updated = ws.update_template(template).unwrap();
                assert_eq!(updated.contents[0].tag, "changed_tag");
                (updated.clone(), ws)
            };
    
            WebSite::update_page_rec_after_template_update(&mut ws.root_page, &updated);
            for p in ws.get_all_pages_as_vec() {
                assert_eq!(p.template.as_ref().unwrap().contents[0].tag, "changed_tag");
            }
            remove_test_dir(&test_dir);
        }
    
    
        #[test]
        fn test_remove_page() {
            let test_dir = PathBuf::from("./test");
            let mut ws = test_website(&test_dir);
            let remove_root_page = ws.remove_page(1);
            assert!(remove_root_page.is_err());
            let remove_subpage = ws.remove_page(2);
            assert!(remove_subpage.is_ok());
            assert!(ws.get_page(2).is_none());
            remove_test_dir(&test_dir);
        }