From dd61f8c0c22718fd674183717dfa71243935fdfc Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Wed, 28 Oct 2020 18:22:43 -0700 Subject: [PATCH] Working on the API that translates the simple vector into a tree-like structure suitable to RESTification. --- server/nm-store/src/lib.rs | 1 + server/nm-trees/.gitignore | 2 + server/nm-trees/Cargo.toml | 15 ++++ server/nm-trees/src/lib.rs | 115 +++++++++++++++++++++++++++++++ server/nm-trees/src/make_tree.rs | 45 ++++++++++++ server/nm-trees/src/structs.rs | 27 ++++++++ 6 files changed, 205 insertions(+) create mode 100644 server/nm-trees/.gitignore create mode 100644 server/nm-trees/Cargo.toml create mode 100644 server/nm-trees/src/lib.rs create mode 100644 server/nm-trees/src/make_tree.rs create mode 100644 server/nm-trees/src/structs.rs diff --git a/server/nm-store/src/lib.rs b/server/nm-store/src/lib.rs index 968f449..f526296 100644 --- a/server/nm-store/src/lib.rs +++ b/server/nm-store/src/lib.rs @@ -6,6 +6,7 @@ mod structs; pub use crate::errors::NoteStoreError; pub use crate::store::NoteStore; +pub use crate::structs::{RawPage, RawNote, NewPage, NewNote}; #[cfg(test)] mod tests { diff --git a/server/nm-trees/.gitignore b/server/nm-trees/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/server/nm-trees/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/server/nm-trees/Cargo.toml b/server/nm-trees/Cargo.toml new file mode 100644 index 0000000..40dcf76 --- /dev/null +++ b/server/nm-trees/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nm-trees" +version = "0.1.0" +authors = ["Elf M. Sternberg "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nm-store = { path = "../nm-store" } +thiserror = "1.0.20" +tokio = { version = "0.2.22", features = ["rt-threaded", "blocking"] } +serde = { version = "1.0.116", features = ["derive"] } +serde_json = "1.0.56" +chrono = { version = "0.4.18", features = ["serde"] } diff --git a/server/nm-trees/src/lib.rs b/server/nm-trees/src/lib.rs new file mode 100644 index 0000000..fccf560 --- /dev/null +++ b/server/nm-trees/src/lib.rs @@ -0,0 +1,115 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//! # Tree Layer +//! +//! This layer provides an interface between the storage layer and +//! the outside world. It provides all of the basic logic, including +//! the premise that a note without a parent is automatically +//! made a child of the day's notepad. + +mod make_tree; +mod structs; + +use nm_store::{NoteStore, NoteStoreError, NewNote}; +use crate::structs::Page; +use crate::make_tree::make_tree; + +#[derive(Debug)] +pub struct Notesmachine(pub(crate) NoteStore); + +type Result = core::result::Result; + +impl Notesmachine { + pub async fn new(url: &str) -> Result { + let notestore = NoteStore::new(url).await?; + Ok(Notesmachine(notestore)) + } + + pub async fn navigate_via_slug(&self, slug: &str) -> Result { + let (rawpage, rawnotes) = self.0.get_page_by_slug(slug).await?; + Ok(make_tree(&rawpage, &rawnotes)) + } + + pub async fn get_box(&self, title: &str) -> Result { + let (rawpage, rawnotes) = self.0.get_page_by_title(title).await?; + Ok(make_tree(&rawpage, &rawnotes)) + } + + // TODO: + // You should be able to: + // Add a note that has no parent (gets added to "today") + // Add a note that specifies only the page (gets added to page/root) + // Add a note that has no position (gets tacked onto the end of the above) + // Add a note that specifies the date of creation. + pub async fn add_note(&self, note: &NewNote) -> Result<()> { + todo!(); + } + + pub async fn add_note_to_page(&self, note: &NewNote) -> Result<()> { + todo!(); + } + + pub async fn add_note_to_today(&self, note: &NewNote) -> Result<()> { + todo!(); + } + + pub async fn reference_note(&self, note_id: &str, new_parent_id: &str, new_position: i64) -> Result<()> { + todo!(); + } + + pub async fn embed_note(&self, note_id: &str, new_parent_id: &str, new_position: i64) -> Result<()> { + todo!(); + } + + pub async fn move_note(&self, note_id: &str, old_parent_id: &str, new_parent_id: &str, position: i64) -> Result<()> { + todo!(); + } + + pub async fn update_note(&self, note_id: &str, content: &str) -> Result<()> { + todo!(); + } + + pub async fn delete_note(&self, note_id: &str) -> Result<()> { + todo!(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tokio; + + async fn fresh_inmemory_database() -> Notesmachine { + let notesmachine = Notesmachine::new("sqlite://:memory:").await; + assert!(notesmachine.is_ok(), "{:?}", notesmachine); + let notesmachine = notesmachine.unwrap(); + let reset = notesmachine.0.reset_database().await; + assert!(reset.is_ok(), "{:?}", reset); + notesmachine + } + + #[tokio::test(threaded_scheduler)] + async fn fetching_unfound_page_by_slug_works() { + let notesmachine = fresh_inmemory_database().await; + let unfoundpage = notesmachine.navigate_via_slug("nonexistent-slug").await; + assert!(unfoundpage.is_err()); + } + + #[tokio::test(threaded_scheduler)] + async fn fetching_unfound_page_by_title_works() { + let title = "Nonexistent Page"; + let notesmachine = fresh_inmemory_database().await; + let newpageresult = notesmachine.get_box(&title).await; + assert!(newpageresult.is_ok(), "{:?}", newpageresult); + + let newpage = newpageresult.unwrap(); + assert_eq!(newpage.title, title, "{:?}", newpage.title); + assert_eq!(newpage.slug, "nonexistent-page", "{:?}", newpage.slug); + assert_eq!(newpage.root_note.content, "", "{:?}", newpage.root_note.content); + assert_eq!(newpage.root_note.notetype, "root", "{:?}", newpage.root_note.notetype); + assert_eq!(newpage.root_note.children.len(), 0, "{:?}", newpage.root_note.children); + } +} + diff --git a/server/nm-trees/src/make_tree.rs b/server/nm-trees/src/make_tree.rs new file mode 100644 index 0000000..cd121a2 --- /dev/null +++ b/server/nm-trees/src/make_tree.rs @@ -0,0 +1,45 @@ +use crate::structs::{Note, Page}; +use nm_store::{RawNote, RawPage}; + +fn make_note_tree(rawnotes: &[RawNote], root: i64) -> Note { + let the_note = rawnotes.iter().find(|note| note.id == root).unwrap().clone(); + + // The special case of the root node must be filtered out here to + // prevent the first pass from smashing the stack in an infinite + // loop. The root node is identified by the type 'root' and + // having its `id` and `parent_id` be equal. Numeric comparisons + // are faster. + let mut children = rawnotes + .iter() + .filter(|note| note.parent_id == root && note.id != root) + .map(|note| make_note_tree(rawnotes, note.id)) + .collect::>(); + children.sort_unstable_by(|a, b| a.position.cmp(&b.position)); + + Note { + uuid: the_note.uuid, + parent_uuid: the_note.parent_uuid, + content: the_note.content, + notetype: the_note.notetype, + position: the_note.position, + creation_date: the_note.creation_date, + updated_date: the_note.updated_date, + lastview_date: the_note.updated_date, + deleted_date: the_note.deleted_date, + children: vec![], + } +} + +pub(crate) fn make_tree(rawpage: &RawPage, rawnotes: &[RawNote]) -> Page { + let the_page = rawpage.clone(); + + Page { + slug: the_page.slug, + title: the_page.title, + creation_date: the_page.creation_date, + updated_date: the_page.updated_date, + lastview_date: the_page.updated_date, + deleted_date: the_page.deleted_date, + root_note: make_note_tree(rawnotes, rawpage.note_id), + } +} diff --git a/server/nm-trees/src/structs.rs b/server/nm-trees/src/structs.rs new file mode 100644 index 0000000..f26fa92 --- /dev/null +++ b/server/nm-trees/src/structs.rs @@ -0,0 +1,27 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct Note { + pub uuid: String, + pub parent_uuid: String, + pub content: String, + pub position: i64, + pub notetype: String, + pub creation_date: DateTime, + pub updated_date: DateTime, + pub lastview_date: DateTime, + pub deleted_date: Option>, + pub children: Vec, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct Page { + pub slug: String, + pub title: String, + pub creation_date: DateTime, + pub updated_date: DateTime, + pub lastview_date: DateTime, + pub deleted_date: Option>, + pub root_note: Note, +}