diff --git a/server/nm-store/docs/TODO.md b/server/nm-store/docs/TODO.md new file mode 100644 index 0000000..a96835b --- /dev/null +++ b/server/nm-store/docs/TODO.md @@ -0,0 +1,2 @@ +[ ] Add RelationshipKind to Notes passed out +[ ] Add KastenKind to Backreferences passed out diff --git a/server/nm-store/src/lib.rs b/server/nm-store/src/lib.rs index 27fda81..014575b 100644 --- a/server/nm-store/src/lib.rs +++ b/server/nm-store/src/lib.rs @@ -6,7 +6,8 @@ mod structs; pub use crate::errors::NoteStoreError; pub use crate::store::NoteStore; -pub use crate::structs::{Note, NoteKind}; +pub use crate::structs::{Note, NoteKind, NoteRelationship, KastenRelationship}; + #[cfg(test)] mod tests { @@ -81,22 +82,22 @@ mod tests { // <- 2 <- 4 let note1 = make_new_note("1"); - let note1_id = storagepool.add_note(¬e1, &root.id, 0).await; + let note1_id = storagepool.add_note(¬e1, &root.id, Some(0)).await; assert!(note1_id.is_ok(), "{:?}", note1_id); let note1_id = note1_id.unwrap(); let note2 = make_new_note("2"); - let note2_id = storagepool.add_note(¬e2, &root.id, 0).await; + let note2_id = storagepool.add_note(¬e2, &root.id, Some(0)).await; assert!(note2_id.is_ok(), "{:?}", note2_id); let note2_id = note2_id.unwrap(); let note3 = make_new_note("3"); - let note3_id = storagepool.add_note(¬e3, ¬e1_id, 0).await; + let note3_id = storagepool.add_note(¬e3, ¬e1_id, Some(0)).await; assert!(note3_id.is_ok(), "{:?}", note3_id); let _note3_id = note3_id.unwrap(); let note4 = make_new_note("4"); - let note4_id = storagepool.add_note(¬e4, ¬e2_id, 0).await; + let note4_id = storagepool.add_note(¬e4, ¬e2_id, Some(0)).await; assert!(note4_id.is_ok(), "{:?}", note4_id); let _note4_id = note4_id.unwrap(); diff --git a/server/nm-store/src/sql/initialize_database.sql b/server/nm-store/src/sql/initialize_database.sql index 3ad220d..38e6084 100644 --- a/server/nm-store/src/sql/initialize_database.sql +++ b/server/nm-store/src/sql/initialize_database.sql @@ -16,7 +16,7 @@ CREATE TABLE notes ( CREATE INDEX note_ids ON notes (id); CREATE TABLE favorites ( - id TEXT NOT NULL, + id TEXT NOT NULL UNIQUE, location INTEGER NOT NULL, FOREIGN KEY (id) REFERENCES notes (id) ON DELETE CASCADE ); @@ -33,6 +33,7 @@ CREATE TABLE note_relationships ( -- If either note disappears, we want all the edges to disappear as well. FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE CASCADE, FOREIGN KEY (parent_id) REFERENCES notes (id) ON DELETE CASCADE, + UNIQUE (note_id, parent_id), CHECK (note_id <> parent_id) ); @@ -45,6 +46,7 @@ CREATE TABLE note_kasten_relationships ( -- If either note disappears, we want all the edges to disappear as well. FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE CASCADE, FOREIGN KEY (kasten_id) REFERENCES notes (id) ON DELETE CASCADE, + UNIQUE (note_id, kasten_id), CHECK (note_id <> kasten_id) ); diff --git a/server/nm-store/src/store.rs b/server/nm-store/src/store.rs index 844cc2a..0c9d8a5 100644 --- a/server/nm-store/src/store.rs +++ b/server/nm-store/src/store.rs @@ -137,7 +137,7 @@ impl NoteStore { Ok((vec![Note::from(zettlekasten)], vec![])) } - pub async fn add_note(&self, note: &NewNote, parent_id: &str, location: i64) -> NoteResult { + pub async fn add_note(&self, note: &NewNote, parent_id: &str, location: Option) -> NoteResult { self.insert_note( note, &ParentId(parent_id.to_string()), @@ -229,16 +229,18 @@ impl NoteStore { &self, note: &NewNote, parent_id: &ParentId, - location: i64, + location: Option, kind: RelationshipKind, ) -> NoteResult { - if location < 0 { - return Err(NoteStoreError::InvalidNoteStructure( - "Add note: A negative position is not valid.".to_string(), - )); - } + if let Some(location) = location { + if location < 0 { + return Err(NoteStoreError::InvalidNoteStructure( + "Add note: A negative location is not valid.".to_string(), + )); + } + } - if parent_id.is_empty() { + if parent_id.is_empty() { return Err(NoteStoreError::InvalidNoteStructure( "Add note: A parent note ID is required.".to_string(), )); @@ -259,10 +261,14 @@ impl NoteStore { let references = build_references(¬e.content); let mut tx = self.0.begin().await?; - let location = cmp::min( - assert_max_child_location_for_note(&mut tx, parent_id).await? + 1, - location, - ); + let location = { + let max_child = assert_max_child_location_for_note(&mut tx, parent_id).await? + 1; + if let Some(location) = location { + cmp::min(max_child, location) + } else { + max_child + } + }; let note_id = NoteId(note.id.clone()); insert_note(&mut tx, ¬e).await?; diff --git a/server/nm-store/src/structs.rs b/server/nm-store/src/structs.rs index 3f19aaa..493b5e2 100644 --- a/server/nm-store/src/structs.rs +++ b/server/nm-store/src/structs.rs @@ -198,7 +198,7 @@ pub(crate) struct NoteRelationshipRow { } #[derive(Clone, Debug)] -pub(crate) struct NoteRelationship { +pub struct NoteRelationship { pub parent_id: String, pub note_id: String, pub location: i64, @@ -224,7 +224,7 @@ pub(crate) struct KastenRelationshipRow { } #[derive(Clone, Debug)] -pub(crate) struct KastenRelationship { +pub struct KastenRelationship { pub note_id: String, pub kasten_id: String, pub kind: KastenRelationshipKind, diff --git a/server/nm-trees/src/lib.rs b/server/nm-trees/src/lib.rs index b23ec50..2e280a2 100644 --- a/server/nm-trees/src/lib.rs +++ b/server/nm-trees/src/lib.rs @@ -13,28 +13,41 @@ mod make_tree; mod structs; use nm_store::{NoteStore, NoteStoreError, NewNote}; -use crate::structs::Page; -use crate::make_tree::make_tree; +use crate::structs::{Page, Note}; +use crate::make_tree::{make_note_tree, make_backreferences}; #[derive(Debug)] pub struct Notesmachine(pub(crate) NoteStore); type Result = core::result::Result; +pub fn make_page(foundtree: &Note, backreferences: Vec>) -> Page { + Page { + slug: foundtree.id, + title: foundtree.content, + creation_date: foundtree.creation_date, + updated_date: foundtree.updated_date, + lastview_date: foundtree.lastview_date, + deleted_date: foundtree.deleted_date, + notes: foundtree.children, + backreferences: backreferences + } +} + impl Notesmachine { pub async fn new(url: &str) -> Result { let notestore = NoteStore::new(url).await?; Ok(Notesmachine(notestore)) } - pub async fn get_box_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_page_via_slug(&self, slug: &str) -> Result { + let (rawtree, rawbackreferences) = self.0.get_kasten_by_slug(slug).await?; + Ok(make_page(&make_note_tree(&rawtree), make_backreferences(&rawbackreferences))) } - 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)) + pub async fn get_page(&self, title: &str) -> Result { + let (rawtree, rawbackreferences) = self.0.get_kasten_by_title(title).await?; + Ok(make_page(&make_note_tree(&rawtree), make_backreferences(&rawbackreferences))) } // TODO: diff --git a/server/nm-trees/src/make_tree.rs b/server/nm-trees/src/make_tree.rs index cd121a2..51fe394 100644 --- a/server/nm-trees/src/make_tree.rs +++ b/server/nm-trees/src/make_tree.rs @@ -1,8 +1,12 @@ use crate::structs::{Note, Page}; -use nm_store::{RawNote, RawPage}; +use nm_store::NoteKind; -fn make_note_tree(rawnotes: &[RawNote], root: i64) -> Note { - let the_note = rawnotes.iter().find(|note| note.id == root).unwrap().clone(); +fn make_note_tree_from(rawnotes: &[nm_store::Note], root_id: &str) -> Note { + let the_note = { + let foundroots: Vec<&nm_store::Note> = rawnotes.iter().filter(|note| note.id == root_id).collect(); + debug_assert!(foundroots.len() == 1); + foundroots.iter().next().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 @@ -11,35 +15,61 @@ fn make_note_tree(rawnotes: &[RawNote], root: i64) -> Note { // are faster. let mut children = rawnotes .iter() - .filter(|note| note.parent_id == root && note.id != root) - .map(|note| make_note_tree(rawnotes, note.id)) + .filter(|note| note.parent_id.is_some() && note.parent_id.unwrap() == root_id && note.id != the_note.id) + .map(|note| make_note_tree_from(rawnotes, ¬e.id)) .collect::>(); - children.sort_unstable_by(|a, b| a.position.cmp(&b.position)); + children.sort_unstable_by(|a, b| a.location.cmp(&b.location)); Note { - uuid: the_note.uuid, - parent_uuid: the_note.parent_uuid, + id: the_note.id, + parent_id: the_note.parent_id, content: the_note.content, - notetype: the_note.notetype, - position: the_note.position, + kind: the_note.kind.to_string(), + location: the_note.location, 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![], + children: children, } } -pub(crate) fn make_tree(rawpage: &RawPage, rawnotes: &[RawNote]) -> Page { - let the_page = rawpage.clone(); +pub(crate) fn make_note_tree(rawnotes: &[nm_store::Note]) -> Note { + let the_root = { + let foundroots: Vec<&nm_store::Note> = rawnotes.iter().filter(|note| note.kind == NoteKind::Kasten).collect(); + debug_assert!(foundroots.len() == 1); + foundroots.iter().next().unwrap().clone() + }; + make_note_tree_from(&rawnotes, &the_root.id) +} - 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), +fn add_child(rawnotes: &[nm_store::Note], acc: &mut Vec, note_id: &str) -> Vec { + let child = rawnotes + .iter() + .find(|note| note.parent_id.is_some() && note.parent_id.unwrap() == note_id); + if let Some(c) = child { + acc.push(Note { + id: c.id, + parent_id: Some(note_id.to_string()), + content: c.content, + kind: c.kind.to_string(), + location: c.location, + creation_date: c.creation_date, + updated_date: c.updated_date, + lastview_date: c.updated_date, + deleted_date: c.deleted_date, + children: vec![], + }); + add_child(rawnotes, acc, &c.id) + } else { + acc.to_vec() } } + +pub(crate) fn make_backreferences(rawnotes: &[nm_store::Note]) -> Vec> { + rawnotes + .iter() + .filter(|note| note.parent_id.is_none() && note.kind == NoteKind::Kasten) + .map(|root| add_child(rawnotes, &mut Vec::::new(), &root.id)) + .collect() +} diff --git a/server/nm-trees/src/structs.rs b/server/nm-trees/src/structs.rs index f26fa92..8278ad8 100644 --- a/server/nm-trees/src/structs.rs +++ b/server/nm-trees/src/structs.rs @@ -3,11 +3,11 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Serialize, Deserialize, Debug)] pub struct Note { - pub uuid: String, - pub parent_uuid: String, + pub id: String, + pub parent_id: Option, pub content: String, - pub position: i64, - pub notetype: String, + pub location: i64, + pub kind: String, pub creation_date: DateTime, pub updated_date: DateTime, pub lastview_date: DateTime, @@ -15,7 +15,6 @@ pub struct Note { pub children: Vec, } -#[derive(Clone, Serialize, Deserialize, Debug)] pub struct Page { pub slug: String, pub title: String, @@ -23,5 +22,6 @@ pub struct Page { pub updated_date: DateTime, pub lastview_date: DateTime, pub deleted_date: Option>, - pub root_note: Note, + pub notes: Vec, + pub backreferences: Vec>, }