From 739ff93427e00dd59e3cd2d6fda315cf2e1c9f1c Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Fri, 16 Oct 2020 07:16:57 -0700 Subject: [PATCH] Note/Page reference relationships now built. --- server/nm-store-cli/Cargo.toml | 3 + server/nm-store/README.org | 6 +- server/nm-store/src/reference_parser.rs | 12 ++++ server/nm-store/src/row_structs.rs | 6 ++ server/nm-store/src/store.rs | 90 ++++++++++++++++++++++++- server/nm-store/src/structs.rs | 25 ------- 6 files changed, 109 insertions(+), 33 deletions(-) diff --git a/server/nm-store-cli/Cargo.toml b/server/nm-store-cli/Cargo.toml index 2a654e5..592af5b 100644 --- a/server/nm-store-cli/Cargo.toml +++ b/server/nm-store-cli/Cargo.toml @@ -3,7 +3,10 @@ name = "nm-store-cli" version = "0.1.0" authors = ["Elf M. Sternberg "] edition = "2018" +description = "Command-line direct access to the notesmachine store." +readme = "./README.org" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + diff --git a/server/nm-store/README.org b/server/nm-store/README.org index 63b2037..370efbb 100644 --- a/server/nm-store/README.org +++ b/server/nm-store/README.org @@ -14,14 +14,10 @@ representations: ** Plans -*** TODO Make it possible to save a note -*** TODO Make it possible to retrieve a note -*** TODO Read how others use SQLX to initialize the database -*** TODO Implement CLI features *** TODO Make it possible to connect two notes -*** TODO Make it possible to save a page *** TODO Make it possible to connect a note to a page *** TODO Make it possible to retrieve a collection of notes *** TODO Make it possible to retrieve a page + diff --git a/server/nm-store/src/reference_parser.rs b/server/nm-store/src/reference_parser.rs index 98c39f2..28060d5 100644 --- a/server/nm-store/src/reference_parser.rs +++ b/server/nm-store/src/reference_parser.rs @@ -84,6 +84,10 @@ fn build_page_titles(references: &Vec) -> Vec { .collect() } +pub(crate) fn build_references(content: &str) -> Vec { + build_page_titles(&find_links(content)) +} + #[cfg(test)] mod tests { use super::*; @@ -122,4 +126,12 @@ Right? [[ ]; assert!(res.iter().eq(expected.iter()), "{:?}", res); } + + #[test] + fn doesnt_crash_on_empty() { + let sample = ""; + let res = build_page_titles(&find_links(sample)); + let expected: Vec = vec![]; + assert!(res.iter().eq(expected.iter()), "{:?}", res); + } } diff --git a/server/nm-store/src/row_structs.rs b/server/nm-store/src/row_structs.rs index 6db81e4..f9dad66 100644 --- a/server/nm-store/src/row_structs.rs +++ b/server/nm-store/src/row_structs.rs @@ -77,6 +77,12 @@ pub(crate) struct JustId { pub id: i64, } +#[derive(Clone, Serialize, Deserialize, Debug, FromRow)] +pub(crate) struct PageTitles { + pub id: i64, + pub title: String, +} + #[derive(Clone, Serialize, Deserialize, Debug, FromRow)] pub(crate) struct NoteRelationship { pub parent_id: i64, diff --git a/server/nm-store/src/store.rs b/server/nm-store/src/store.rs index 09da501..0f23063 100644 --- a/server/nm-store/src/store.rs +++ b/server/nm-store/src/store.rs @@ -1,11 +1,14 @@ use crate::errors::NoteStoreError; use crate::row_structs::{ JustId, JustSlugs, NewNote, NewNoteBuilder, NewPage, NewPageBuilder, NoteRelationship, RawNote, + PageTitles, RawPage, }; +use crate::reference_parser::build_references; use friendly_id; use lazy_static::lazy_static; use regex::Regex; +use std::collections::HashSet; use shrinkwraprs::Shrinkwrap; use slug::slugify; use sqlx; @@ -17,11 +20,14 @@ use std::collections::HashMap; use std::sync::Arc; #[derive(Shrinkwrap, Copy, Clone)] -struct ParentId(i64); +struct PageId(i64); #[derive(Shrinkwrap, Copy, Clone)] struct NoteId(i64); +#[derive(Shrinkwrap, Copy, Clone)] +struct ParentId(i64); + /// A handle to our Sqlite database. #[derive(Clone, Debug)] pub struct NoteStore(Arc); @@ -108,11 +114,34 @@ impl NoteStore { ) -> NoteResult { let mut new_note = note.clone(); new_note.uuid = friendly_id::create(); + let references = build_references(¬e.content); let mut tx = self.0.begin().await?; + + // Start by building the note and putting it into its relationship. let parent_id = select_note_id_for_uuid(&mut tx, parent_note_uuid).await?; let new_note_id = insert_one_new_note(&mut tx, &new_note).await?; let _ = make_room_for_new_note(&mut tx, parent_id, position).await?; let _ = insert_note_note_relationship(&mut tx, parent_id, new_note_id, position).await?; + + // From the references, make lists of pages that exist, and pages + // that do not. + let found_references = find_all_references_for(&mut tx, &references).await?; + let new_references = diff_references(&references, &found_references); + let mut known_reference_ids: Vec = Vec::new(); + + // Create the pages that don't exist + for one_reference in new_references.iter() { + let new_root_note = create_unique_root_note(); + let new_root_note_id = insert_one_new_note(&mut tx, &new_root_note).await?; + let new_page_slug = generate_slug(&mut tx, &one_reference).await?; + let new_page = create_new_page_for(&one_reference, &new_page_slug, new_root_note_id); + known_reference_ids.push(insert_one_new_page(&mut tx, &new_page).await?) + }; + + // And associate the note with all the pages. + known_reference_ids.append(&mut found_references.iter().map(|r| PageId(r.id)).collect()); + let _ = insert_note_page_references(&mut tx, new_note_id, &known_reference_ids).await?; + tx.commit().await?; Ok(new_note.uuid) } @@ -369,7 +398,7 @@ where } } -async fn insert_one_new_page<'a, E>(executor: E, page: &NewPage) -> SqlResult +async fn insert_one_new_page<'a, E>(executor: E, page: &NewPage) -> SqlResult where E: Executor<'a, Database = Sqlite>, { @@ -384,7 +413,7 @@ where "VALUES (?, ?, ?, ?, ?, ?);" ); - Ok(NoteId( + Ok(PageId( sqlx::query(insert_one_page_sql) .bind(&page.slug) .bind(&page.title) @@ -495,6 +524,51 @@ where .map(|_| ()) } +async fn find_all_references_for<'a, E>( + executor: E, + references: &Vec, +) -> SqlResult> +where + E: Executor<'a, Database = Sqlite>, +{ + let find_all_references_for_sql = "SELECT id, title FROM pages WHERE title IN (" + .to_string() + + &["?"].repeat(references.len()).join(",") + + &");".to_string(); + + let mut request = sqlx::query_as(&find_all_references_for_sql); + for id in references.iter() { + request = request.bind(id); + } + request + .fetch_all(executor) + .await +} + +async fn insert_note_page_references<'a, E>( + executor: E, + note_id: NoteId, + references: &Vec, +) -> SqlResult<()> +where + E: Executor<'a, Database = Sqlite>, +{ + let insert_note_page_references_sql = + "INSERT INTO note_page_references (note_id, page_id) VALUES ".to_string() + + &["(?, ?)"].repeat(references.len()).join(", ") + + &";".to_string(); + + let mut request = sqlx::query(&insert_note_page_references_sql); + for reference in references { + request = request.bind(&*note_id).bind(**reference); + } + + request + .execute(executor) + .await + .map(|_| ()) +} + fn create_unique_root_note() -> NewNote { NewNoteBuilder::default() .uuid(friendly_id::create()) @@ -512,3 +586,13 @@ fn create_new_page_for(title: &str, slug: &str, note_id: NoteId) -> NewPage { .build() .unwrap() } + +// Given the references supplied, and the references found in the datastore, +// return a list of the references not found in the datastore. +fn diff_references(references: &Vec, found_references: &Vec) -> Vec { + let all: HashSet = references.iter().cloned().collect(); + let found: HashSet = found_references.iter().map(|r| r.title.clone()).collect(); + all.difference(&found).cloned().collect() +} + + diff --git a/server/nm-store/src/structs.rs b/server/nm-store/src/structs.rs index 4988201..858618f 100644 --- a/server/nm-store/src/structs.rs +++ b/server/nm-store/src/structs.rs @@ -1,31 +1,6 @@ use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use sqlx::{self, FromRow}; -#[derive(Clone, Serialize, Deserialize, Debug, FromRow)] -pub(crate) struct RawPage { - pub id: i64, - pub slug: String, - pub title: String, - pub note_id: i64, - pub creation_date: DateTime, - pub updated_date: DateTime, - pub lastview_date: DateTime, - pub deleted_date: Option>, -} - -#[derive(Clone, Serialize, Deserialize, Debug, FromRow)] -pub(crate) struct RawNote { - pub id: i64, - pub uuid: String, - pub content: String, - pub notetype: String, - pub creation_date: DateTime, - pub updated_date: DateTime, - pub lastview_date: DateTime, - pub deleted_date: Option>, -} - // // A Resource is either content or a URL to content that the // // user embeds in a note. TODO: I have no idea how to do this yet, // // but I'll figure it out.