From fd4f39b5b84809e01db830a8828b27366c0ee25e Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Fri, 9 Oct 2020 09:57:43 -0700 Subject: [PATCH] =?UTF-8?q?It=20compiles.=20Whether=20it=20works=20is=20st?= =?UTF-8?q?ill=20=C2=AF\=5F(=E3=83=84)=5F/=C2=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/nm-store/src/lib.rs | 2 +- server/nm-store/src/store.rs | 96 +++++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 34 deletions(-) diff --git a/server/nm-store/src/lib.rs b/server/nm-store/src/lib.rs index 8f0cb10..80cf490 100644 --- a/server/nm-store/src/lib.rs +++ b/server/nm-store/src/lib.rs @@ -8,8 +8,8 @@ pub use crate::store::NoteStore; #[cfg(test)] mod tests { - use chrono; use super::*; + use chrono; use tokio; async fn fresh_inmemory_database() -> NoteStore { diff --git a/server/nm-store/src/store.rs b/server/nm-store/src/store.rs index ed399ff..52a4794 100644 --- a/server/nm-store/src/store.rs +++ b/server/nm-store/src/store.rs @@ -1,5 +1,6 @@ use crate::errors::NoteStoreError; -use crate::row_structs::{JustSlugs, NewNote, NewPage, RawNote, RawPage}; +use crate::row_structs::{JustSlugs, NewNote, NewNoteBuilder, NewPage, NewPageBuilder, RawNote, RawPage}; +use friendly_id; use lazy_static::lazy_static; use regex::Regex; use slug::slugify; @@ -49,17 +50,34 @@ impl NoteStore { Ok((page, vec![])) } + /// Fetch page by title + /// + /// Supports the use case of the user navigating to a page via + /// the page's formal title. Since the title is the key reference + /// of the system, if no page with that title is found, a page with + /// that title is generated automatically. pub async fn get_page_by_title(&self, title: &str) -> NoteResult<(RawPage, Vec)> { let mut tx = self.0.begin().await?; let page = match select_page_by_title(&mut tx, title).await { Ok(page) => page, - Err(sqlx::Error::RowNotFound) => match create_page_for_title(&mut tx, title).await { - Ok(page) => page, - Err(e) => return Err(NoteStoreError::DBError(e)) + Err(sqlx::Error::RowNotFound) => { + let new_page = { + 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, title).await?; + let new_page = create_new_page_for(&title, &new_page_slug, new_root_note_id); + let new_page_id = insert_one_new_page(&mut tx, &new_page).await?; + select_page_by_title(&mut tx, title).await + }; + match new_page { + Ok(page) => page, + 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?; return Ok((page, vec![])); } } @@ -75,9 +93,17 @@ impl NoteStore { // coherent and easily readable, and hides away the gnarliness of some // of the SQL queries. -async fn select_page_by_slug<'e, E>(executor: E, slug: &str) -> SqlResult +async fn reset_database<'a, E>(executor: E) -> SqlResult<()> where - E: 'e + Executor<'e, Database = Sqlite>, + E: Executor<'a, Database = Sqlite> +{ + let initialize_sql = include_str!("sql/initialize_database.sql"); + sqlx::query(initialize_sql).execute(executor).await.map(|_| ()) +} + +async fn select_page_by_slug<'a, E>(executor: E, slug: &str) -> SqlResult +where + E: Executor<'a, Database = Sqlite> { let select_one_page_by_slug_sql = concat!( "SELECT id, title, slug, note_id, creation_date, updated_date, ", @@ -89,9 +115,9 @@ where .await?) } -async fn select_page_by_title<'e, E>(executor: E, title: &str) -> SqlResult +async fn select_page_by_title<'a, E>(executor: E, title: &str) -> SqlResult where - E: 'e + Executor<'e, Database = Sqlite>, + E: Executor<'a, Database = Sqlite>, { let select_one_page_by_title_sql = concat!( "SELECT id, title, slug, note_id, creation_date, updated_date, ", @@ -103,28 +129,20 @@ where .await?) } -async fn reset_database<'e, E>(executor: E) -> SqlResult<()> +async fn get_note_collection_for_root<'a, E>(executor: E, root: i64) -> SqlResult> where - E: 'e + Executor<'e, Database = Sqlite>, -{ - let initialize_sql = include_str!("sql/initialize_database.sql"); - sqlx::query(initialize_sql).execute(executor).await.map(|_| ()) -} - -async fn get_note_collection_for_root<'e, E>(executor: E, root: i64) -> SqlResult> -where - E: 'e + Executor<'e, Database = Sqlite>, + E: Executor<'a, Database = Sqlite>, { let select_note_collection_for_root = include_str!("sql/select_note_collection_from_root.sql"); Ok(sqlx::query_as(&select_note_collection_for_root) - .bind(&root) - .fetch_all(executor) - .await?) + .bind(&root) + .fetch_all(executor) + .await?) } -async fn insert_one_new_note<'e, E>(executor: E, note: &NewNote) -> SqlResult +async fn insert_one_new_note<'a, E>(executor: E, note: &NewNote) -> SqlResult where - E: 'e + Executor<'e, Database = Sqlite>, + E: Executor<'a, Database = Sqlite>, { let insert_one_note_sql = concat!( "INSERT INTO notes ( ", @@ -170,9 +188,9 @@ fn find_maximal_slug(slugs: &Vec) -> Option { // Given an initial string and an existing collection of slugs, // generate a new slug that does not conflict with the current // collection. -async fn generate_slug<'e, E>(executor: E, title: &str) -> SqlResult +async fn generate_slug<'a, E>(executor: E, title: &str) -> SqlResult where - E: 'e + Executor<'e, Database = Sqlite>, + E: Executor<'a, Database = Sqlite> { lazy_static! { static ref RE_STRIP_NUM: Regex = Regex::new(r"-\d+$").unwrap(); @@ -180,7 +198,7 @@ where let initial_slug = slugify(title); let sample_slug = RE_STRIP_NUM.replace_all(&initial_slug, ""); - let slug_finder_sql = "SELECT slug FROM pages WHERE slug LIKE '?%';"; + let slug_finder_sql = "SELECT slug FROM pages WHERE slug LIKE '?%';"; let similar_slugs: Vec = sqlx::query_as(&slug_finder_sql) .bind(&*sample_slug) .fetch_all(executor) @@ -192,9 +210,9 @@ where } } -async fn insert_one_new_page<'e, E>(executor: E, page: &NewPage) -> SqlResult +async fn insert_one_new_page<'a, E>(executor: E, page: &NewPage) -> SqlResult where - E: 'e + Executor<'e, Database = Sqlite>, + E: Executor<'a, Database = Sqlite>, { let insert_one_page_sql = concat!( "INSERT INTO pages ( ", @@ -219,9 +237,21 @@ where .last_insert_rowid()) } -async fn create_page_for_title<'e, E>(_executor: E, _title: &str) -> SqlResult -where - E: 'e + Executor<'e, Database = Sqlite>, -{ - todo!() +fn create_unique_root_note() -> NewNote { + NewNoteBuilder::default() + .uuid(friendly_id::create()) + .content("".to_string()) + .notetype("root".to_string()) + .build() + .unwrap() } + +fn create_new_page_for(title: &str, slug: &str, note_id: i64) -> NewPage { + NewPageBuilder::default() + .slug(slug.to_string()) + .title(title.to_string()) + .note_id(note_id) + .build() + .unwrap() +} +