diff --git a/README.md b/README.md index 6aeedee..6ed9837 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,11 @@ TODO Pagination - Update - Search - meilisearch -GoodreadID, GoogleID, Description, Excerpt - -Publishers - ISBN - mass fill - different format +GoodreadID, GoogleID, Description, Excerpt, Publishers GoogleAPI fallback diff --git a/backend/Cargo.lock b/backend/Cargo.lock index b6e26ad..de3bb36 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -440,6 +440,7 @@ dependencies = [ "image", "itertools", "log", + "meilisearch-sdk", "reqwest", "sea-orm", "serde", diff --git a/backend/api/Cargo.toml b/backend/api/Cargo.toml index 29c62b1..63a7638 100644 --- a/backend/api/Cargo.toml +++ b/backend/api/Cargo.toml @@ -15,6 +15,7 @@ dotenvy = "0.15.0" image = "0.24" error-chain = "0.12.4" log = "^0.4" +meilisearch-sdk = "0.20.1" reqwest = {version = "0.11.11", features = ["json"]} serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" diff --git a/backend/api/src/lib.rs b/backend/api/src/lib.rs index 520ef72..0949631 100644 --- a/backend/api/src/lib.rs +++ b/backend/api/src/lib.rs @@ -18,12 +18,15 @@ use tower_http::services::ServeDir; use tower_http::cors::{Any,CorsLayer}; use tower_http::trace::TraceLayer; +use booksman_search::BookMeili; +use booksman_search; use booksman_orm::{ sea_orm::{Database, DatabaseConnection}, Mutation as MutationCore, Query as QueryCore, - BookAndMeta, +// BookAndMeta, }; -use itertools::Itertools; +use meilisearch_sdk::client::Client; +//use itertools::Itertools; use ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn}; use std::env; @@ -91,6 +94,48 @@ struct BookPageOL { struct DescriptionOL { value: Option, } +/* +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +enum DescriptionOL { + DescriptionValueString{value: String}, + DescriptionString(String), +} +*/ +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BooksManyISBN { + #[serde(flatten)] + pub book_items: HashMap, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BookISBNItem { + pub key: String, + pub title: String, + pub authors: Vec, + pub identifiers: Option, + pub publish_date: Option, + pub subjects: Option>, + pub subject_places: Option>, + pub subject_people: Option>, + pub subject_times: Option>, + pub cover: Option, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Identifiers { + pub goodreads: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Subject { + pub name: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Cover { + pub medium: String, +} impl Books { @@ -99,7 +144,7 @@ impl Books { match book.cover_i { Some(cover_i) => { book.cover_url = Some(format!( - "https://covers.openlibrary.org/b/id/{}-L.jpg", + "https://covers.openlibrary.org/b/id/{}-M.jpg", cover_i )) } @@ -107,6 +152,7 @@ impl Books { } } } + /* async fn set_all_descriptions(&mut self) { for book in self.docs.iter_mut() { let query = format!("https://openlibrary.org/{}.json", book.key); @@ -124,6 +170,7 @@ impl Books { } } } + */ } async fn handle_error(_err: io::Error) -> impl IntoResponse { @@ -165,23 +212,30 @@ pub async fn main() { dotenvy::dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); + let meili_url = env::var("MEILI_URL").expect("MEILI_URL is not set in .env file"); + let meili_key = env::var("MEILI_KEY").expect("MEILI_KEY is not set in .env file"); + let conn = Database::connect(db_url) .await .expect("Database connection failed"); //Migrator::up(&conn, None).await.unwrap(); + let meili_client = Client::new(meili_url, meili_key); let app = Router::new() .route("/api/search_openlibrary", get(search_openlibrary)) + .route("/api/create_by_isbn", get(create_by_isbn)) .route("/api/delete/:id", get(delete_book)) .route("/api/list", get(list_book)) + .route("/api/list_search", get(list_search_book)) .route("/api/create", post(create_book)) .route("/api/update", post(update_book)) .nest("/images", get_service(ServeDir::new("../images")).handle_error(handle_error)) .merge(SpaRouter::new("/assets", opt.static_dir)) .layer(ServiceBuilder::new().layer(TraceLayer::new_for_http())) .layer(Extension(conn)) - .layer( + .layer(Extension(meili_client)) + .layer( // see https://docs.rs/tower-http/latest/tower_http/cors/index.html // for more details // @@ -207,6 +261,161 @@ pub async fn main() { .expect("Unable to start server"); } +//https://openlibrary.org/api/books?bibkeys=ISBN:9780980200447&jscmd=data&format=json +async fn create_by_isbn( + Extension(ref conn): Extension, + Extension(ref meili_client): Extension, + axum::extract::Query(params): axum::extract::Query>, +) -> impl IntoResponse { + print!("Get items with query params: {:?}\n", params); + let isbns = params.get("isbns").unwrap(); + let splitisbns : Vec = isbns.split(",").map(|s| format!("ISBN:{}",s.to_string())).collect(); + let joinedisbns = splitisbns.join(","); + let query = format!("https://openlibrary.org/api/books?bibkeys={}&jscmd=data&format=json", joinedisbns); + let res = reqwest::get(query).await.expect("Unable to request"); + let resjson: BooksManyISBN = res.json::().await.expect("Unable to return value"); + for isbnstring in splitisbns.iter() { + let mut cover = resjson.clone().book_items.get(isbnstring).unwrap().cover.clone().unwrap().medium.clone(); + if !resjson.book_items.get(isbnstring).unwrap().cover.is_none() { + let img_bytes = reqwest::get(cover.clone()).await.unwrap().bytes().await.unwrap(); + let image = image::load_from_memory(&img_bytes).unwrap(); + let temp_cover = cover.clone(); + let img_id = temp_cover.split("/").last().unwrap(); + image.save(format!("../images/{}",img_id)).expect("Failed to save image"); + cover = img_id.to_string(); + } + let doc_sent = BookUI{ + id: 1, + open_library_key: Some(resjson.clone().book_items.get(isbnstring).unwrap().key.clone()), + title: resjson.clone().book_items.get(isbnstring).unwrap().title.clone(), + edition_count: None, + first_publish_year: None, + median_page_count: None, + goodread_id: Some(resjson.clone().book_items.get(isbnstring).unwrap().identifiers.clone().unwrap().goodreads.unwrap().get(0).unwrap().to_string()), + description: None, + cover: Some(cover.clone()), + location: None, + time_added: None, + rating: None, + comments: Some("From openlibrary".to_string()), + author_name: Some(resjson.clone().book_items.get(isbnstring).unwrap().authors.iter().map(|s| s.name.clone()).collect()), + person: Some(resjson.clone().book_items.get(isbnstring).unwrap().subject_people.clone().unwrap().iter().map(|s| s.name.clone()).collect()), + place: Some(resjson.clone().book_items.get(isbnstring).unwrap().subject_places.clone().unwrap().iter().map(|s| s.name.clone()).collect()), + subject: Some(resjson.clone().book_items.get(isbnstring).unwrap().subjects.clone().unwrap().iter().map(|s| s.name.clone()).collect()), + time: Some(resjson.clone().book_items.get(isbnstring).unwrap().subject_times.clone().unwrap().iter().map(|s| s.name.clone()).collect()), + isbn: Some(vec![isbnstring.split("ISBN:").last().unwrap().to_string()]) + }; + let book: book::Model = book::Model{ + open_library_key: doc_sent.open_library_key.to_owned(), + title: (doc_sent.title.to_owned()), + edition_count: doc_sent.edition_count.to_owned(), + first_publish_year: (doc_sent.first_publish_year.to_owned()), + median_page_count: (doc_sent.median_page_count.to_owned()), + goodread_id: doc_sent.goodread_id.to_owned(), + description: doc_sent.description.to_owned(), + comments: doc_sent.comments.to_owned(), + cover: Some(cover.to_owned()), + rating: doc_sent.rating.to_owned(), + time_added: doc_sent.time_added.to_owned(), + id: 1, + location: doc_sent.location.to_owned(), + }; + let created_book = MutationCore::create_book(conn, book) + .await + .expect("could not create book"); + + for author_name in doc_sent.author_name.as_ref().unwrap().iter() { + let record : book_author::Model = book_author::Model{ + id: 1, + book_id: (created_book.last_insert_id), + author_name: author_name.to_owned(), + }; + MutationCore::create_book_author(conn, record) + .await + .expect("could not create book"); + } + for person in doc_sent.person.as_ref().unwrap().iter() { + let record : book_person::Model = book_person::Model{ + id: 1, + book_id: (created_book.last_insert_id), + person: person.to_owned(), + }; + MutationCore::create_book_person(conn, record) + .await + .expect("could not create book"); + + } + for place in doc_sent.place.as_ref().unwrap().iter() { + let record : book_place::Model = book_place::Model{ + id: 1, + book_id: (created_book.last_insert_id), + place: place.to_owned(), + }; + MutationCore::create_book_place(conn, record) + .await + .expect("could not create book"); + + } + for subject in doc_sent.subject.as_ref().unwrap().iter() { + let record : book_subject::Model = book_subject::Model{ + id: 1, + book_id: (created_book.last_insert_id), + subject: subject.to_owned(), + }; + MutationCore::create_book_subject(conn, record) + .await + .expect("could not create book"); + + } + for time in doc_sent.time.as_ref().unwrap().iter() { + let record : book_time::Model = book_time::Model{ + id: 1, + book_id: (created_book.last_insert_id), + time: time.to_owned(), + }; + MutationCore::create_book_time(conn, record) + .await + .expect("could not create book"); + + } + for isbn in doc_sent.isbn.as_ref().unwrap().iter() { + let record : book_isbn::Model = book_isbn::Model{ + id: 1, + book_id: (created_book.last_insert_id), + isbn: isbn.to_owned(), + }; + MutationCore::create_book_isbn(conn, record) + .await + .expect("could not create book"); + + } + let book_meili = BookMeili{ + id: doc_sent.id, + open_library_key: doc_sent.open_library_key.unwrap_or("".to_string()), + title: doc_sent.title, + edition_count: doc_sent.edition_count.unwrap_or(0), + first_publish_year: doc_sent.first_publish_year.unwrap_or(0), + median_page_count: doc_sent.median_page_count.unwrap_or(0), + goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()), + description: doc_sent.description.unwrap_or("".to_string()), + cover: doc_sent.cover.unwrap_or("".to_string()), + location: doc_sent.location.unwrap_or("".to_string()), + time_added: doc_sent.time_added.unwrap_or("".to_string()), + rating: doc_sent.rating.unwrap_or(0), + comments: doc_sent.comments.unwrap_or("".to_string()), + author_name: doc_sent.author_name.unwrap_or(vec!["".to_string()]), + person: doc_sent.person.unwrap_or(vec!["".to_string()]), + place: doc_sent.place.unwrap_or(vec!["".to_string()]), + subject: doc_sent.subject.unwrap_or(vec!["".to_string()]), + time: doc_sent.time.unwrap_or(vec!["".to_string()]), + isbn: doc_sent.isbn.unwrap_or(vec!["".to_string()]), + }; + booksman_search::create_or_update_book(book_meili, meili_client).await; + } + return "Done"; +} + + async fn search_openlibrary( axum::extract::Query(params): axum::extract::Query>, ) -> impl IntoResponse { @@ -223,27 +432,27 @@ let mut vec = Vec::with_capacity(12); for i in 0..12 as usize { let doc = resjson.docs[i].clone(); vec.push( -BookUI{ - id: -((i+1) as i32), -open_library_key: Some(doc.key), -title: doc.title, -edition_count: doc.edition_count, -first_publish_year: doc.first_publish_year, -median_page_count: doc.number_of_pages_median, -goodread_id: doc.goodread_id, -description: doc.description, -cover: doc.cover_url, -location: None, -time_added: None, -rating: None, -comments: None, -author_name: doc.author_name, -person: doc.person, -place: doc.place, -subject: doc.subject, -time: doc.time, -isbn: doc.isbn -} + BookUI{ + id: -((i+1) as i32), + open_library_key: Some(doc.key), + title: doc.title, + edition_count: doc.edition_count, + first_publish_year: doc.first_publish_year, + median_page_count: doc.number_of_pages_median, + goodread_id: doc.goodread_id, + description: doc.description, + cover: doc.cover_url, + location: None, + time_added: None, + rating: None, + comments: Some("From openlibrary".to_string()), + author_name: doc.author_name, + person: doc.person, + place: doc.place, + subject: doc.subject, + time: doc.time, + isbn: doc.isbn + } ); } return Json(vec); @@ -251,11 +460,13 @@ isbn: doc.isbn async fn delete_book( Extension(ref conn): Extension, + Extension(ref meili_client): Extension, Path(id): Path, ) -> impl IntoResponse { MutationCore::delete_book(conn, id) .await .expect("could not delete book"); + booksman_search::delete_book(id, meili_client).await; "success" } @@ -309,23 +520,75 @@ return Json(res); } +async fn list_search_book( + Extension(ref meili_client): Extension, + axum::extract::Query(params): axum::extract::Query>, +) -> impl IntoResponse { + let page: usize = params.get("page").unwrap().parse().unwrap(); + let search = params.get("search").unwrap(); + + let books = booksman_search::search_book(search, page,meili_client) + .await + .expect("could not list books"); + +let mut resbooks: Vec = Vec::with_capacity(24); + + for bookmeili in books.into_iter() { + let mut cover = bookmeili.clone().cover; + if cover!="".to_string() { + cover = format!("http://localhost:8081/images/{}",cover); + } + let bookui = BookUI{ + id: bookmeili.id, + title: bookmeili.title, + open_library_key: Some(bookmeili.open_library_key), + edition_count: Some(bookmeili.edition_count), + first_publish_year: Some(bookmeili.first_publish_year), + median_page_count: Some(bookmeili.median_page_count), + goodread_id: Some(bookmeili.goodread_id), + description: Some(bookmeili.description), + cover: Some(cover.clone()), + location: Some(bookmeili.location), + time_added: Some(bookmeili.time_added), + rating: Some(bookmeili.rating), + comments: Some(bookmeili.comments), + author_name: Some(bookmeili.author_name), + person: Some(bookmeili.person), + place: Some(bookmeili.place), + subject: Some(bookmeili.subject), + time: Some(bookmeili.time), + isbn: Some(bookmeili.isbn), + }; + resbooks.push(bookui); + } + + let res = PaginatedBookUIList{ + num_pages: 10, + books: resbooks + }; +return Json(res); + // "success" +} + + async fn create_book( Extension(ref conn): Extension, + Extension(ref meili_client): Extension, Json(doc_sent): Json, ) -> impl IntoResponse { -println!("Creating book"); -let mut cover = doc_sent.cover.clone(); -if !doc_sent.cover.is_none() { - let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap(); - //.expect("Could not fetch image"); - //let img_bytes = img_resp.unwrap().bytes(); - let image = image::load_from_memory(&img_bytes).unwrap(); - let temp_cover = doc_sent.cover.clone().unwrap(); - let img_id = temp_cover.split("/").last().unwrap(); - image.save(format!("../images/{}",img_id)).expect("Failed to save image"); - cover = Some(img_id.to_string()); -} -let book: book::Model = book::Model{ + println!("Creating book"); + let mut cover = doc_sent.cover.clone(); + if !doc_sent.cover.is_none() { + let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap(); + //.expect("Could not fetch image"); + //let img_bytes = img_resp.unwrap().bytes(); + let image = image::load_from_memory(&img_bytes).unwrap(); + let temp_cover = doc_sent.cover.clone().unwrap(); + let img_id = temp_cover.split("/").last().unwrap(); + image.save(format!("../images/{}",img_id)).expect("Failed to save image"); + cover = Some(img_id.to_string()); + } + let book: book::Model = book::Model{ open_library_key: doc_sent.open_library_key.to_owned(), title: (doc_sent.title.to_owned()), edition_count: doc_sent.edition_count.to_owned(), @@ -409,7 +672,29 @@ let book: book::Model = book::Model{ .expect("could not create book"); } - + let book_meili = BookMeili{ + id: doc_sent.id, + open_library_key: doc_sent.open_library_key.unwrap_or("".to_string()), + title: doc_sent.title, + edition_count: doc_sent.edition_count.unwrap_or(0), + first_publish_year: doc_sent.first_publish_year.unwrap_or(0), + median_page_count: doc_sent.median_page_count.unwrap_or(0), + goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()), + description: doc_sent.description.unwrap_or("".to_string()), + cover: doc_sent.cover.unwrap_or("".to_string()), + location: doc_sent.location.unwrap_or("".to_string()), + time_added: doc_sent.time_added.unwrap_or("".to_string()), + rating: doc_sent.rating.unwrap_or(0), + comments: doc_sent.comments.unwrap_or("".to_string()), + author_name: doc_sent.author_name.unwrap_or(vec!["".to_string()]), + person: doc_sent.person.unwrap_or(vec!["".to_string()]), + place: doc_sent.place.unwrap_or(vec!["".to_string()]), + subject: doc_sent.subject.unwrap_or(vec!["".to_string()]), + time: doc_sent.time.unwrap_or(vec!["".to_string()]), + isbn: doc_sent.isbn.unwrap_or(vec!["".to_string()]), + }; + booksman_search::create_or_update_book(book_meili, meili_client).await; + "success" } @@ -418,6 +703,7 @@ let book: book::Model = book::Model{ async fn update_book( Extension(ref conn): Extension, + Extension(ref meili_client): Extension, Json(doc_sent): Json, ) -> impl IntoResponse { println!("Updating book"); @@ -447,7 +733,7 @@ let book: book::Model = book::Model{ id: doc_sent.id.to_owned(), location: doc_sent.location.to_owned(), }; - let created_book = MutationCore::update_book_by_id(conn, book.id, book) + MutationCore::update_book_by_id(conn, book.id, book) .await .expect("could not update book"); @@ -540,6 +826,28 @@ let book: book::Model = book::Model{ } + let book_meili = BookMeili{ + id: doc_sent.id, + open_library_key: doc_sent.open_library_key.unwrap_or("".to_string()), + title: doc_sent.title, + edition_count: doc_sent.edition_count.unwrap_or(0), + first_publish_year: doc_sent.first_publish_year.unwrap_or(0), + median_page_count: doc_sent.median_page_count.unwrap_or(0), + goodread_id: doc_sent.goodread_id.unwrap_or("".to_string()), + description: doc_sent.description.unwrap_or("".to_string()), + cover: doc_sent.cover.unwrap_or("".to_string()), + location: doc_sent.location.unwrap_or("".to_string()), + time_added: doc_sent.time_added.unwrap_or("".to_string()), + rating: doc_sent.rating.unwrap_or(0), + comments: doc_sent.comments.unwrap_or("".to_string()), + author_name: doc_sent.author_name.unwrap_or(vec!["".to_string()]), + person: doc_sent.person.unwrap_or(vec!["".to_string()]), + place: doc_sent.place.unwrap_or(vec!["".to_string()]), + subject: doc_sent.subject.unwrap_or(vec!["".to_string()]), + time: doc_sent.time.unwrap_or(vec!["".to_string()]), + isbn: doc_sent.isbn.unwrap_or(vec!["".to_string()]), + }; + booksman_search::create_or_update_book(book_meili, meili_client).await; "success" } diff --git a/backend/search/src/lib.rs b/backend/search/src/lib.rs index a00cede..dbe1e1e 100644 --- a/backend/search/src/lib.rs +++ b/backend/search/src/lib.rs @@ -1,30 +1,59 @@ -use meilisearch_sdk::{client::*, search::*}; +use meilisearch_sdk::client::*; +use meilisearch_sdk::search::*; use serde::{Serialize, Deserialize}; - -#[derive(Serialize, Deserialize, Debug)] -struct Movie { - id: usize, - title: String, - genres: Vec, +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct BookMeili { + pub id: i32, + pub open_library_key: String, + pub title: String, + pub edition_count: i32, + pub first_publish_year: i32, + pub median_page_count: i32, + pub goodread_id: String, + pub description: String, + pub cover: String, + pub location: String, + pub time_added: String, + pub rating: i32, + pub comments: String, + pub author_name: Vec, + pub person: Vec, + pub place: Vec, + pub subject: Vec, + pub time: Vec, + pub isbn: Vec, } -#[tokio::main] -async fn main() { - // Create a client (without sending any request so that can't fail) - let client = Client::new(MEILISEARCH_URL, MEILISEARCH_API_KEY); - +pub async fn create_or_update_book(book: BookMeili, client: &Client) { // An index is where the documents are stored. - let movies = client.index("movies"); - + let books = client.index("books"); // Add some movies in the index. If the index 'movies' does not exist, Meilisearch creates it when you first add the documents. - movies.add_documents(&[ - Movie { id: 1, title: String::from("Carol"), genres: vec!["Romance".to_string(), "Drama".to_string()] }, - Movie { id: 2, title: String::from("Wonder Woman"), genres: vec!["Action".to_string(), "Adventure".to_string()] }, - Movie { id: 3, title: String::from("Life of Pi"), genres: vec!["Adventure".to_string(), "Drama".to_string()] }, - Movie { id: 4, title: String::from("Mad Max"), genres: vec!["Adventure".to_string(), "Science Fiction".to_string()] }, - Movie { id: 5, title: String::from("Moana"), genres: vec!["Fantasy".to_string(), "Action".to_string()] }, - Movie { id: 6, title: String::from("Philadelphia"), genres: vec!["Drama".to_string()] }, + books.add_or_replace(&[ + book ], Some("id")).await.unwrap(); - } + + +pub async fn delete_book(bookid: i32, client: &Client) { + // An index is where the documents are stored. + let books = client.index("books"); + books.delete_document(bookid).await.unwrap(); +} + + +pub async fn search_book(search: &str, page: usize, client: &Client) -> Result, meilisearch_sdk::errors::Error> { + // An index is where the documents are stored. + let books = client.index("books"); + let results : SearchResults = books.search().with_query(search).with_offset((page-1)*24) + .execute::().await.unwrap(); + + let formatted_results : Vec = (results.hits).iter().map(|r| r.result.clone()).collect(); + //.iter()s.map(|r| r.formatted_result.unwrap()).collect(); + return Ok(formatted_results); +} + + + + + diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 9eae6db..18ec9fa 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -49,6 +49,8 @@ pub struct AppState { pub books: RcSignal>, pub search: RcSignal, pub openlibrary: RcSignal, + pub internalsearch: RcSignal, + pub pagenum: RcSignal, pub adding: RcSignal, pub updating: RcSignal, pub displaying: RcSignal, @@ -74,6 +76,14 @@ async fn fetch_books(search: String) -> Result, reqwasm::Error> { Ok(body) } +async fn search_books(search: String, page: u32) -> Result, reqwasm::Error> { + let url = format!("http://localhost:8081/api/list_search?search={}&page={}", search, page); + let resp = Request::get(&url).send().await?; + println!("Fetching books\n"); + let body = resp.json::().await?; + Ok(body) +} + async fn add_book(record: BookUI) -> Result { let url = format!("http://localhost:8081/api/create"); let resp = Request::post(&url).body(serde_wasm_bindgen::to_value(&serde_json::to_string(&record).unwrap()).unwrap()).header("content-type","application/json").send().await?; @@ -115,7 +125,9 @@ pub fn Header(cx: Scope) -> View { if !task.is_empty() { app_state.search.set(task); - app_state.openlibrary.set(true); + app_state.openlibrary.set(true); + app_state.internalsearch.set(false); + info!("Fetching search\n"); value.set("".to_string()); input_ref @@ -125,11 +137,34 @@ pub fn Header(cx: Scope) -> View { } } }; - let click_listall = |_| (app_state.openlibrary.set(false)); + let click_listall = |_| { + app_state.openlibrary.set(false); + app_state.internalsearch.set(false); + + }; + + let click_searchall = |_| { + + let mut task = value.get().as_ref().clone(); + task = task.trim().to_string(); + + if !task.is_empty() { + app_state.search.set(task); + app_state.openlibrary.set(false); + app_state.internalsearch.set(true); + value.set("".to_string()); + input_ref + .get::() + .unchecked_into::() + .set_value(""); + } + }; + let click_addbook = |_| { app_state.adding.set(true); - app_state.addingbook.set(BookUI::default()); + app_state.addingbook.set(BookUI::default()); }; + view! { cx, header(class="header") { h1 { "Search OpenLibrary" } @@ -140,7 +175,8 @@ pub fn Header(cx: Scope) -> View { on:keyup=handle_submit, ) button(on:click=click_listall) { "List All DB" } - button(on:click=click_addbook) { "+ Add New" } + button(on:click=click_searchall) { "Search internal" } + button(on:click=click_addbook) { "+ Add New" } } } } @@ -150,16 +186,31 @@ async fn ListDB(cx: Scope<'_>) -> View { let app_state = use_context::(cx); create_effect(cx, || { let app_state = app_state.clone(); - + app_state.search.track(); + app_state.pagenum.track(); + if *app_state.openlibrary.get() == false { - spawn_local(async move { - app_state.books.set( - list_books(1) - .await - .unwrap().books, - ) - }); + if *app_state.internalsearch.get() == false { + spawn_local(async move { + app_state.books.set( + list_books(app_state.pagenum.get()) + .await + .unwrap().books, + ) + }); + } else { + spawn_local(async move { + app_state.books.set( + search_books(app_state.search.get().to_string(), app_state.pagenum.get()) + .await + .unwrap(), + ) + } + } } + + + }); let docs = create_memo(cx, || app_state.books.get().iter().cloned().collect::>()); @@ -187,12 +238,14 @@ async fn ListDB(cx: Scope<'_>) -> View { #[component(inline_props)] pub fn BookDB(cx: Scope, bookitem: BookUIProp) -> View { let app_state = use_context::(cx); - let book = bookitem.bookitem.clone(); let bookdelete = bookitem.bookitem.clone(); + let bookupdate = bookitem.bookitem.clone(); + let bookdisplay = bookitem.bookitem.clone(); + let coverurl = book.cover.clone().unwrap_or("http://localhost:8081/images/placeholder.jpg".to_string()); let handle_delete = move |_| { spawn_local(async move { - let temp = delete_book(book.id).await.unwrap(); + let temp = delete_book(bookdelete.id).await.unwrap(); println!("{}",temp.status()); }); }; @@ -200,9 +253,14 @@ pub fn BookDB(cx: Scope, bookitem: BookUIProp) -> View { let handle_update = move |_| { app_state.adding.set(false); app_state.updating.set(true); - app_state.addingbook.set(bookdelete.clone()); + app_state.addingbook.set(bookupdate.clone()); }; + let handle_display = move |_| { + app_state.displaying.set(true); + app_state.updating.set(false); + app_state.displayingbook.set(bookdisplay.clone()); + }; view! { cx, div(class="column"){ div(class="card"){ @@ -211,6 +269,8 @@ pub fn BookDB(cx: Scope, bookitem: BookUIProp) -> View { (format!("{:?}",book)) button(class="delete", on:click=handle_delete){ "-" } button(class="update", on:click=handle_update){ "=" } + button(class="info", on:click=handle_display){ "+" } + } } @@ -228,13 +288,16 @@ async fn ListOL(cx: Scope<'_>) -> View { //); let app_state = app_state.clone(); app_state.search.track(); + spawn_local(async move { if *app_state.search.get() != "" { - app_state.books.set( - fetch_books(app_state.search.get().to_string()) - .await - .unwrap(), - ) + if *app_state.openlibrary.get() == true { + app_state.books.set( + fetch_books(app_state.search.get().to_string() ) + .await + .unwrap(), + ) + } } }); }); @@ -406,12 +469,36 @@ info!("Adding book"); } +#[component] +async fn SelectedUI(cx: Scope<'_>) -> View { + let app_state = use_context::(cx); + let displ_book = create_signal(cx, (*app_state.displayingbook.get()).clone()); + + let handle_update = move |_| { + app_state.displaying.set(false); + }; + view! {cx, + (if *app_state.displaying.get() == true { + p { + div(class="select-book"){ + img(src=coverurl,width="100") + (format!("{:?}",book)) + button(class="close", on:click=handle_close){ "CLOSE" } + } + } + } + }) +} + + #[component] fn App(cx: Scope) -> View { let app_state = AppState { books: create_rc_signal(Vec::new()), search: create_rc_signal(String::default()), openlibrary: create_rc_signal(bool::default()), + internalsearch: create_rc_signal(bool::default()), + pagenum: create_rc_signal(1), adding: create_rc_signal(bool::default()), updating: create_rc_signal(bool::default()), addingbook: create_rc_signal(BookUI::default()), @@ -424,6 +511,7 @@ fn App(cx: Scope) -> View { div { Header {} AddingUI{} + SelectedUI{} ListOL {} ListDB{} }