FEAT: It is now theoretically possible to add a nested note.

This commit is contained in:
Elf M. Sternberg 2020-10-11 12:14:44 -07:00
parent 1b36183edb
commit 380d3f4a7c
2 changed files with 306 additions and 218 deletions

View File

@ -67,10 +67,15 @@ pub(crate) struct JustSlugs {
} }
#[derive(Clone, Serialize, Deserialize, Debug, FromRow)] #[derive(Clone, Serialize, Deserialize, Debug, FromRow)]
pub struct JustTitles { pub(crate) struct JustTitles {
title: String, title: String,
} }
#[derive(Clone, Serialize, Deserialize, Debug, FromRow)]
pub(crate) struct JustId {
pub id: i64,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,5 +1,7 @@
use crate::errors::NoteStoreError; use crate::errors::NoteStoreError;
use crate::row_structs::{JustSlugs, NewNote, NewNoteBuilder, NewPage, NewPageBuilder, RawNote, RawPage}; use crate::row_structs::{
JustId, JustSlugs, NewNote, NewNoteBuilder, NewPage, NewPageBuilder, RawNote, RawPage,
};
use friendly_id; use friendly_id;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
@ -28,7 +30,9 @@ impl NoteStore {
// to its original empty form. Do not use unless you // to its original empty form. Do not use unless you
// really, really want that to happen. // really, really want that to happen.
pub async fn reset_database(&self) -> NoteResult<()> { pub async fn reset_database(&self) -> NoteResult<()> {
reset_database(&*self.0).await.map_err(NoteStoreError::DBError) reset_database(&*self.0)
.await
.map_err(NoteStoreError::DBError)
} }
/// Fetch page by slug /// Fetch page by slug
@ -61,7 +65,10 @@ impl NoteStore {
let (page, notes) = match select_page_by_title(&mut tx, title).await { let (page, notes) = match select_page_by_title(&mut tx, title).await {
Ok(page) => { Ok(page) => {
let note_id = page.note_id; let note_id = page.note_id;
(page, select_note_collection_from_root(&mut tx, note_id).await?) (
page,
select_note_collection_from_root(&mut tx, note_id).await?,
)
} }
Err(sqlx::Error::RowNotFound) => { Err(sqlx::Error::RowNotFound) => {
let page = { let page = {
@ -73,13 +80,32 @@ impl NoteStore {
select_page_by_title(&mut tx, &title).await? select_page_by_title(&mut tx, &title).await?
}; };
let note_id = page.note_id; let note_id = page.note_id;
(page, select_note_collection_from_root(&mut tx, note_id).await?) (
}, page,
select_note_collection_from_root(&mut tx, note_id).await?,
)
}
Err(e) => return Err(NoteStoreError::DBError(e)), Err(e) => return Err(NoteStoreError::DBError(e)),
}; };
// Todo: Replace vec with the results of the CTE
tx.commit().await?; tx.commit().await?;
return Ok((page, notes)); Ok((page, notes))
}
pub async fn insert_nested_note(
&self,
note: NewNote,
parent_note_uuid: &str,
position: i32,
) -> NoteResult<String> {
let mut new_note = note.clone();
new_note.uuid = friendly_id::create();
let mut tx = self.0.begin().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 _ = 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?;
tx.commit().await?;
Ok(new_note.uuid)
} }
} }
@ -96,15 +122,18 @@ impl NoteStore {
async fn reset_database<'a, E>(executor: E) -> SqlResult<()> async fn reset_database<'a, E>(executor: E) -> SqlResult<()>
where where
E: Executor<'a, Database = Sqlite> E: Executor<'a, Database = Sqlite>,
{ {
let initialize_sql = include_str!("sql/initialize_database.sql"); let initialize_sql = include_str!("sql/initialize_database.sql");
sqlx::query(initialize_sql).execute(executor).await.map(|_| ()) sqlx::query(initialize_sql)
.execute(executor)
.await
.map(|_| ())
} }
async fn select_page_by_slug<'a, E>(executor: E, slug: &str) -> SqlResult<RawPage> async fn select_page_by_slug<'a, E>(executor: E, slug: &str) -> SqlResult<RawPage>
where where
E: Executor<'a, Database = Sqlite> E: Executor<'a, Database = Sqlite>,
{ {
let select_one_page_by_slug_sql = concat!( let select_one_page_by_slug_sql = concat!(
"SELECT id, title, slug, note_id, creation_date, updated_date, ", "SELECT id, title, slug, note_id, creation_date, updated_date, ",
@ -130,11 +159,66 @@ where
.await?) .await?)
} }
async fn select_note_id_for_uuid<'a, E>(executor: E, uuid: &str) -> SqlResult<i64>
where
E: Executor<'a, Database = Sqlite>,
{
let select_note_id_for_uuid_sql = "SELECT id FROM notes WHERE uuid = ?";
let id: JustId = sqlx::query_as(&select_note_id_for_uuid_sql)
.bind(&uuid)
.fetch_one(executor)
.await?;
Ok(id.id)
}
async fn make_room_for_new_note<'a, E>(executor: E, parent_id: i64, position: i32) -> SqlResult<()>
where
E: Executor<'a, Database = Sqlite>,
{
let make_room_for_new_note_sql = concat!(
"UPDATE note_relationships ",
"SET position = position + 1 ",
"WHERE position >= ? and parent_id = ?;"
);
sqlx::query(make_room_for_new_note_sql)
.bind(&position)
.bind(&parent_id)
.execute(executor)
.await
.map(|_| ())
}
async fn insert_note_note_relationship<'a, E>(
executor: E,
parent_id: i64,
note_id: i64,
position: i32,
) -> SqlResult<()>
where
E: Executor<'a, Database = Sqlite>,
{
let insert_note_note_relationship_sql = concat!(
"INSERT INTO note_relationships (parent_id, note_id, position, nature) ",
"values (?, ?, ?, ?)"
);
sqlx::query(insert_note_note_relationship_sql)
.bind(&parent_id)
.bind(&note_id)
.bind(&position)
.bind("note")
.execute(executor)
.await
.map(|_| ())
}
async fn select_note_collection_from_root<'a, E>(executor: E, root: i64) -> SqlResult<Vec<RawNote>> async fn select_note_collection_from_root<'a, E>(executor: E, root: i64) -> SqlResult<Vec<RawNote>>
where where
E: Executor<'a, Database = Sqlite>, E: Executor<'a, Database = Sqlite>,
{ {
let select_note_collection_from_root_sql = include_str!("sql/select_note_collection_from_root.sql"); let select_note_collection_from_root_sql =
include_str!("sql/select_note_collection_from_root.sql");
Ok(sqlx::query_as(&select_note_collection_from_root_sql) Ok(sqlx::query_as(&select_note_collection_from_root_sql)
.bind(&root) .bind(&root)
.fetch_all(executor) .fetch_all(executor)
@ -191,7 +275,7 @@ fn find_maximal_slug(slugs: &Vec<JustSlugs>) -> Option<u32> {
// collection. // collection.
async fn generate_slug<'a, E>(executor: E, title: &str) -> SqlResult<String> async fn generate_slug<'a, E>(executor: E, title: &str) -> SqlResult<String>
where where
E: Executor<'a, Database = Sqlite> E: Executor<'a, Database = Sqlite>,
{ {
lazy_static! { lazy_static! {
static ref RE_STRIP_NUM: Regex = Regex::new(r"-\d+$").unwrap(); static ref RE_STRIP_NUM: Regex = Regex::new(r"-\d+$").unwrap();
@ -255,4 +339,3 @@ fn create_new_page_for(title: &str, slug: &str, note_id: i64) -> NewPage {
.build() .build()
.unwrap() .unwrap()
} }