-
Pierre Jarriges authoredPierre Jarriges authored
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);
}
}