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.45 KiB
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,
    pub 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
    }

    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))
    }
    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)
    }

    fn get_page_parent_mut(&mut self, id: usize) -> Result<&mut Page, String> {
        Self::find_page_parent_mut(&mut self.root_page, None, 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)),
        }
    }

    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),
        }
    }
}

#[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);
    }

    #[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);
    }
}