Note/Page reference relationships now built.

This commit is contained in:
Elf M. Sternberg 2020-10-16 07:16:57 -07:00
parent 0f98dc4523
commit 739ff93427
6 changed files with 109 additions and 33 deletions

View File

@ -3,7 +3,10 @@ name = "nm-store-cli"
version = "0.1.0" version = "0.1.0"
authors = ["Elf M. Sternberg <elf.sternberg@gmail.com>"] authors = ["Elf M. Sternberg <elf.sternberg@gmail.com>"]
edition = "2018" 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@ -14,14 +14,10 @@ representations:
** Plans ** 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 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 connect a note to a page
*** TODO Make it possible to retrieve a collection of notes *** TODO Make it possible to retrieve a collection of notes
*** TODO Make it possible to retrieve a page *** TODO Make it possible to retrieve a page

View File

@ -84,6 +84,10 @@ fn build_page_titles(references: &Vec<String>) -> Vec<String> {
.collect() .collect()
} }
pub(crate) fn build_references(content: &str) -> Vec<String> {
build_page_titles(&find_links(content))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -122,4 +126,12 @@ Right? [[
]; ];
assert!(res.iter().eq(expected.iter()), "{:?}", res); 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<String> = vec![];
assert!(res.iter().eq(expected.iter()), "{:?}", res);
}
} }

View File

@ -77,6 +77,12 @@ pub(crate) struct JustId {
pub id: i64, 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)] #[derive(Clone, Serialize, Deserialize, Debug, FromRow)]
pub(crate) struct NoteRelationship { pub(crate) struct NoteRelationship {
pub parent_id: i64, pub parent_id: i64,

View File

@ -1,11 +1,14 @@
use crate::errors::NoteStoreError; use crate::errors::NoteStoreError;
use crate::row_structs::{ use crate::row_structs::{
JustId, JustSlugs, NewNote, NewNoteBuilder, NewPage, NewPageBuilder, NoteRelationship, RawNote, JustId, JustSlugs, NewNote, NewNoteBuilder, NewPage, NewPageBuilder, NoteRelationship, RawNote,
PageTitles,
RawPage, RawPage,
}; };
use crate::reference_parser::build_references;
use friendly_id; use friendly_id;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use std::collections::HashSet;
use shrinkwraprs::Shrinkwrap; use shrinkwraprs::Shrinkwrap;
use slug::slugify; use slug::slugify;
use sqlx; use sqlx;
@ -17,11 +20,14 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
#[derive(Shrinkwrap, Copy, Clone)] #[derive(Shrinkwrap, Copy, Clone)]
struct ParentId(i64); struct PageId(i64);
#[derive(Shrinkwrap, Copy, Clone)] #[derive(Shrinkwrap, Copy, Clone)]
struct NoteId(i64); struct NoteId(i64);
#[derive(Shrinkwrap, Copy, Clone)]
struct ParentId(i64);
/// A handle to our Sqlite database. /// A handle to our Sqlite database.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct NoteStore(Arc<SqlitePool>); pub struct NoteStore(Arc<SqlitePool>);
@ -108,11 +114,34 @@ impl NoteStore {
) -> NoteResult<String> { ) -> NoteResult<String> {
let mut new_note = note.clone(); let mut new_note = note.clone();
new_note.uuid = friendly_id::create(); new_note.uuid = friendly_id::create();
let references = build_references(&note.content);
let mut tx = self.0.begin().await?; 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 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 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 _ = 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?; 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<PageId> = 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?; tx.commit().await?;
Ok(new_note.uuid) Ok(new_note.uuid)
} }
@ -369,7 +398,7 @@ where
} }
} }
async fn insert_one_new_page<'a, E>(executor: E, page: &NewPage) -> SqlResult<NoteId> async fn insert_one_new_page<'a, E>(executor: E, page: &NewPage) -> SqlResult<PageId>
where where
E: Executor<'a, Database = Sqlite>, E: Executor<'a, Database = Sqlite>,
{ {
@ -384,7 +413,7 @@ where
"VALUES (?, ?, ?, ?, ?, ?);" "VALUES (?, ?, ?, ?, ?, ?);"
); );
Ok(NoteId( Ok(PageId(
sqlx::query(insert_one_page_sql) sqlx::query(insert_one_page_sql)
.bind(&page.slug) .bind(&page.slug)
.bind(&page.title) .bind(&page.title)
@ -495,6 +524,51 @@ where
.map(|_| ()) .map(|_| ())
} }
async fn find_all_references_for<'a, E>(
executor: E,
references: &Vec<String>,
) -> SqlResult<Vec<PageTitles>>
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<PageId>,
) -> 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 { fn create_unique_root_note() -> NewNote {
NewNoteBuilder::default() NewNoteBuilder::default()
.uuid(friendly_id::create()) .uuid(friendly_id::create())
@ -512,3 +586,13 @@ fn create_new_page_for(title: &str, slug: &str, note_id: NoteId) -> NewPage {
.build() .build()
.unwrap() .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<String>, found_references: &Vec<PageTitles>) -> Vec<String> {
let all: HashSet<String> = references.iter().cloned().collect();
let found: HashSet<String> = found_references.iter().map(|r| r.title.clone()).collect();
all.difference(&found).cloned().collect()
}

View File

@ -1,31 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{self, FromRow}; 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<Utc>,
pub updated_date: DateTime<Utc>,
pub lastview_date: DateTime<Utc>,
pub deleted_date: Option<DateTime<Utc>>,
}
#[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<Utc>,
pub updated_date: DateTime<Utc>,
pub lastview_date: DateTime<Utc>,
pub deleted_date: Option<DateTime<Utc>>,
}
// // A Resource is either content or a URL to content that the // // 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, // // user embeds in a note. TODO: I have no idea how to do this yet,
// // but I'll figure it out. // // but I'll figure it out.