Note/Page reference relationships now built.
This commit is contained in:
parent
0f98dc4523
commit
739ff93427
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(¬e.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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue