Refinements to the tree structure.

This commit is contained in:
Elf M. Sternberg 2020-11-05 08:30:02 -08:00
parent 4776541df4
commit 83d5858a45
8 changed files with 109 additions and 55 deletions

View File

@ -0,0 +1,2 @@
[ ] Add RelationshipKind to Notes passed out
[ ] Add KastenKind to Backreferences passed out

View File

@ -6,7 +6,8 @@ mod structs;
pub use crate::errors::NoteStoreError; pub use crate::errors::NoteStoreError;
pub use crate::store::NoteStore; pub use crate::store::NoteStore;
pub use crate::structs::{Note, NoteKind}; pub use crate::structs::{Note, NoteKind, NoteRelationship, KastenRelationship};
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -81,22 +82,22 @@ mod tests {
// <- 2 <- 4 // <- 2 <- 4
let note1 = make_new_note("1"); let note1 = make_new_note("1");
let note1_id = storagepool.add_note(&note1, &root.id, 0).await; let note1_id = storagepool.add_note(&note1, &root.id, Some(0)).await;
assert!(note1_id.is_ok(), "{:?}", note1_id); assert!(note1_id.is_ok(), "{:?}", note1_id);
let note1_id = note1_id.unwrap(); let note1_id = note1_id.unwrap();
let note2 = make_new_note("2"); let note2 = make_new_note("2");
let note2_id = storagepool.add_note(&note2, &root.id, 0).await; let note2_id = storagepool.add_note(&note2, &root.id, Some(0)).await;
assert!(note2_id.is_ok(), "{:?}", note2_id); assert!(note2_id.is_ok(), "{:?}", note2_id);
let note2_id = note2_id.unwrap(); let note2_id = note2_id.unwrap();
let note3 = make_new_note("3"); let note3 = make_new_note("3");
let note3_id = storagepool.add_note(&note3, &note1_id, 0).await; let note3_id = storagepool.add_note(&note3, &note1_id, Some(0)).await;
assert!(note3_id.is_ok(), "{:?}", note3_id); assert!(note3_id.is_ok(), "{:?}", note3_id);
let _note3_id = note3_id.unwrap(); let _note3_id = note3_id.unwrap();
let note4 = make_new_note("4"); let note4 = make_new_note("4");
let note4_id = storagepool.add_note(&note4, &note2_id, 0).await; let note4_id = storagepool.add_note(&note4, &note2_id, Some(0)).await;
assert!(note4_id.is_ok(), "{:?}", note4_id); assert!(note4_id.is_ok(), "{:?}", note4_id);
let _note4_id = note4_id.unwrap(); let _note4_id = note4_id.unwrap();

View File

@ -16,7 +16,7 @@ CREATE TABLE notes (
CREATE INDEX note_ids ON notes (id); CREATE INDEX note_ids ON notes (id);
CREATE TABLE favorites ( CREATE TABLE favorites (
id TEXT NOT NULL, id TEXT NOT NULL UNIQUE,
location INTEGER NOT NULL, location INTEGER NOT NULL,
FOREIGN KEY (id) REFERENCES notes (id) ON DELETE CASCADE 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. -- 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 (note_id) REFERENCES notes (id) ON DELETE CASCADE,
FOREIGN KEY (parent_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) 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. -- 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 (note_id) REFERENCES notes (id) ON DELETE CASCADE,
FOREIGN KEY (kasten_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) CHECK (note_id <> kasten_id)
); );

View File

@ -137,7 +137,7 @@ impl NoteStore {
Ok((vec![Note::from(zettlekasten)], vec![])) Ok((vec![Note::from(zettlekasten)], vec![]))
} }
pub async fn add_note(&self, note: &NewNote, parent_id: &str, location: i64) -> NoteResult<String> { pub async fn add_note(&self, note: &NewNote, parent_id: &str, location: Option<i64>) -> NoteResult<String> {
self.insert_note( self.insert_note(
note, note,
&ParentId(parent_id.to_string()), &ParentId(parent_id.to_string()),
@ -229,16 +229,18 @@ impl NoteStore {
&self, &self,
note: &NewNote, note: &NewNote,
parent_id: &ParentId, parent_id: &ParentId,
location: i64, location: Option<i64>,
kind: RelationshipKind, kind: RelationshipKind,
) -> NoteResult<String> { ) -> NoteResult<String> {
if location < 0 { if let Some(location) = location {
return Err(NoteStoreError::InvalidNoteStructure( if location < 0 {
"Add note: A negative position is not valid.".to_string(), 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( return Err(NoteStoreError::InvalidNoteStructure(
"Add note: A parent note ID is required.".to_string(), "Add note: A parent note ID is required.".to_string(),
)); ));
@ -259,10 +261,14 @@ impl NoteStore {
let references = build_references(&note.content); let references = build_references(&note.content);
let mut tx = self.0.begin().await?; let mut tx = self.0.begin().await?;
let location = cmp::min( let location = {
assert_max_child_location_for_note(&mut tx, parent_id).await? + 1, let max_child = assert_max_child_location_for_note(&mut tx, parent_id).await? + 1;
location, if let Some(location) = location {
); cmp::min(max_child, location)
} else {
max_child
}
};
let note_id = NoteId(note.id.clone()); let note_id = NoteId(note.id.clone());
insert_note(&mut tx, &note).await?; insert_note(&mut tx, &note).await?;

View File

@ -198,7 +198,7 @@ pub(crate) struct NoteRelationshipRow {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct NoteRelationship { pub struct NoteRelationship {
pub parent_id: String, pub parent_id: String,
pub note_id: String, pub note_id: String,
pub location: i64, pub location: i64,
@ -224,7 +224,7 @@ pub(crate) struct KastenRelationshipRow {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct KastenRelationship { pub struct KastenRelationship {
pub note_id: String, pub note_id: String,
pub kasten_id: String, pub kasten_id: String,
pub kind: KastenRelationshipKind, pub kind: KastenRelationshipKind,

View File

@ -13,28 +13,41 @@ mod make_tree;
mod structs; mod structs;
use nm_store::{NoteStore, NoteStoreError, NewNote}; use nm_store::{NoteStore, NoteStoreError, NewNote};
use crate::structs::Page; use crate::structs::{Page, Note};
use crate::make_tree::make_tree; use crate::make_tree::{make_note_tree, make_backreferences};
#[derive(Debug)] #[derive(Debug)]
pub struct Notesmachine(pub(crate) NoteStore); pub struct Notesmachine(pub(crate) NoteStore);
type Result<T> = core::result::Result<T, NoteStoreError>; type Result<T> = core::result::Result<T, NoteStoreError>;
pub fn make_page(foundtree: &Note, backreferences: Vec<Vec<Note>>) -> 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 { impl Notesmachine {
pub async fn new(url: &str) -> Result<Self> { pub async fn new(url: &str) -> Result<Self> {
let notestore = NoteStore::new(url).await?; let notestore = NoteStore::new(url).await?;
Ok(Notesmachine(notestore)) Ok(Notesmachine(notestore))
} }
pub async fn get_box_via_slug(&self, slug: &str) -> Result<Page> { pub async fn get_page_via_slug(&self, slug: &str) -> Result<Page> {
let (rawpage, rawnotes) = self.0.get_page_by_slug(slug).await?; let (rawtree, rawbackreferences) = self.0.get_kasten_by_slug(slug).await?;
Ok(make_tree(&rawpage, &rawnotes)) Ok(make_page(&make_note_tree(&rawtree), make_backreferences(&rawbackreferences)))
} }
pub async fn get_box(&self, title: &str) -> Result<Page> { pub async fn get_page(&self, title: &str) -> Result<Page> {
let (rawpage, rawnotes) = self.0.get_page_by_title(title).await?; let (rawtree, rawbackreferences) = self.0.get_kasten_by_title(title).await?;
Ok(make_tree(&rawpage, &rawnotes)) Ok(make_page(&make_note_tree(&rawtree), make_backreferences(&rawbackreferences)))
} }
// TODO: // TODO:

View File

@ -1,8 +1,12 @@
use crate::structs::{Note, Page}; use crate::structs::{Note, Page};
use nm_store::{RawNote, RawPage}; use nm_store::NoteKind;
fn make_note_tree(rawnotes: &[RawNote], root: i64) -> Note { fn make_note_tree_from(rawnotes: &[nm_store::Note], root_id: &str) -> Note {
let the_note = rawnotes.iter().find(|note| note.id == root).unwrap().clone(); 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 // The special case of the root node must be filtered out here to
// prevent the first pass from smashing the stack in an infinite // 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. // are faster.
let mut children = rawnotes let mut children = rawnotes
.iter() .iter()
.filter(|note| note.parent_id == root && note.id != root) .filter(|note| note.parent_id.is_some() && note.parent_id.unwrap() == root_id && note.id != the_note.id)
.map(|note| make_note_tree(rawnotes, note.id)) .map(|note| make_note_tree_from(rawnotes, &note.id))
.collect::<Vec<Note>>(); .collect::<Vec<Note>>();
children.sort_unstable_by(|a, b| a.position.cmp(&b.position)); children.sort_unstable_by(|a, b| a.location.cmp(&b.location));
Note { Note {
uuid: the_note.uuid, id: the_note.id,
parent_uuid: the_note.parent_uuid, parent_id: the_note.parent_id,
content: the_note.content, content: the_note.content,
notetype: the_note.notetype, kind: the_note.kind.to_string(),
position: the_note.position, location: the_note.location,
creation_date: the_note.creation_date, creation_date: the_note.creation_date,
updated_date: the_note.updated_date, updated_date: the_note.updated_date,
lastview_date: the_note.updated_date, lastview_date: the_note.updated_date,
deleted_date: the_note.deleted_date, deleted_date: the_note.deleted_date,
children: vec![], children: children,
} }
} }
pub(crate) fn make_tree(rawpage: &RawPage, rawnotes: &[RawNote]) -> Page { pub(crate) fn make_note_tree(rawnotes: &[nm_store::Note]) -> Note {
let the_page = rawpage.clone(); 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 { fn add_child(rawnotes: &[nm_store::Note], acc: &mut Vec<Note>, note_id: &str) -> Vec<Note> {
slug: the_page.slug, let child = rawnotes
title: the_page.title, .iter()
creation_date: the_page.creation_date, .find(|note| note.parent_id.is_some() && note.parent_id.unwrap() == note_id);
updated_date: the_page.updated_date, if let Some(c) = child {
lastview_date: the_page.updated_date, acc.push(Note {
deleted_date: the_page.deleted_date, id: c.id,
root_note: make_note_tree(rawnotes, rawpage.note_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<Vec<Note>> {
rawnotes
.iter()
.filter(|note| note.parent_id.is_none() && note.kind == NoteKind::Kasten)
.map(|root| add_child(rawnotes, &mut Vec::<Note>::new(), &root.id))
.collect()
}

View File

@ -3,11 +3,11 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Note { pub struct Note {
pub uuid: String, pub id: String,
pub parent_uuid: String, pub parent_id: Option<String>,
pub content: String, pub content: String,
pub position: i64, pub location: i64,
pub notetype: String, pub kind: String,
pub creation_date: DateTime<Utc>, pub creation_date: DateTime<Utc>,
pub updated_date: DateTime<Utc>, pub updated_date: DateTime<Utc>,
pub lastview_date: DateTime<Utc>, pub lastview_date: DateTime<Utc>,
@ -15,7 +15,6 @@ pub struct Note {
pub children: Vec<Note>, pub children: Vec<Note>,
} }
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Page { pub struct Page {
pub slug: String, pub slug: String,
pub title: String, pub title: String,
@ -23,5 +22,6 @@ pub struct Page {
pub updated_date: DateTime<Utc>, pub updated_date: DateTime<Utc>,
pub lastview_date: DateTime<Utc>, pub lastview_date: DateTime<Utc>,
pub deleted_date: Option<DateTime<Utc>>, pub deleted_date: Option<DateTime<Utc>>,
pub root_note: Note, pub notes: Vec<Note>,
pub backreferences: Vec<Vec<Note>>,
} }