v0.12-searchP3

This commit is contained in:
2022-11-27 17:48:14 +05:30
parent f297d8e4e0
commit db0d976192
6 changed files with 393 additions and 166 deletions

1
backend/Cargo.lock generated
View File

@@ -441,6 +441,7 @@ dependencies = [
"itertools", "itertools",
"log", "log",
"meilisearch-sdk", "meilisearch-sdk",
"migration",
"reqwest", "reqwest",
"sea-orm", "sea-orm",
"serde", "serde",

View File

@@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
booksman-orm = { path = "../orm" } booksman-orm = { path = "../orm" }
booksman-search = { path = "../search" } booksman-search = { path = "../search" }
migration = { path = "../migration" }
entity = { path = "../entity" } entity = { path = "../entity" }
axum = "^0.5" axum = "^0.5"
axum-extra = { version = "^0.3", features = ["spa"] } axum-extra = { version = "^0.3", features = ["spa"] }

View File

@@ -29,6 +29,7 @@ use meilisearch_sdk::client::Client;
//use itertools::Itertools; //use itertools::Itertools;
use ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn}; use ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn};
use std::env; use std::env;
use migration::{Migrator, MigratorTrait};
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
struct BookUI { struct BookUI {
@@ -60,7 +61,7 @@ struct Docs {
edition_count: Option<i32>, edition_count: Option<i32>,
first_publish_year: Option<i32>, first_publish_year: Option<i32>,
number_of_pages_median: Option<i32>, number_of_pages_median: Option<i32>,
goodread_id: Option<String>, id_goodreads: Option<Vec<String>>,
description: Option<String>, description: Option<String>,
isbn: Option<Vec<String>>, isbn: Option<Vec<String>>,
cover_i: Option<i32>, cover_i: Option<i32>,
@@ -211,6 +212,10 @@ pub async fn main() {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file");
let cors_url = env::var("CORS_URL").expect("CORS_URL is not set in .env file");
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); 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_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 meili_key = env::var("MEILI_KEY").expect("MEILI_KEY is not set in .env file");
@@ -219,7 +224,9 @@ pub async fn main() {
let conn = Database::connect(db_url) let conn = Database::connect(db_url)
.await .await
.expect("Database connection failed"); .expect("Database connection failed");
//Migrator::up(&conn, None).await.unwrap(); // Apply all pending migrations
Migrator::up(&conn, None).await.unwrap();
let meili_client = Client::new(meili_url, meili_key); let meili_client = Client::new(meili_url, meili_key);
let app = Router::new() let app = Router::new()
@@ -230,7 +237,7 @@ pub async fn main() {
.route("/api/list_search", get(list_search_book)) .route("/api/list_search", get(list_search_book))
.route("/api/create", post(create_book)) .route("/api/create", post(create_book))
.route("/api/update", post(update_book)) .route("/api/update", post(update_book))
.nest("/images", get_service(ServeDir::new("../images")).handle_error(handle_error)) .nest("/images", get_service(ServeDir::new(images_dir)).handle_error(handle_error))
.merge(SpaRouter::new("/assets", opt.static_dir)) .merge(SpaRouter::new("/assets", opt.static_dir))
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http())) .layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()))
.layer(Extension(conn)) .layer(Extension(conn))
@@ -244,7 +251,7 @@ pub async fn main() {
// or see this issue https://github.com/tokio-rs/axum/issues/849 // or see this issue https://github.com/tokio-rs/axum/issues/849
CorsLayer::new() CorsLayer::new()
.allow_methods([Method::GET, Method::POST]) .allow_methods([Method::GET, Method::POST])
.allow_origin("http://localhost:8080".parse::<HeaderValue>().unwrap()) .allow_origin(cors_url.parse::<HeaderValue>().unwrap())
.allow_headers(Any), .allow_headers(Any),
); );
@@ -267,6 +274,10 @@ async fn create_by_isbn(
Extension(ref meili_client): Extension<Client>, Extension(ref meili_client): Extension<Client>,
axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>, axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
dotenvy::dotenv().ok();
let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file");
print!("Get items with query params: {:?}\n", params); print!("Get items with query params: {:?}\n", params);
let isbns = params.get("isbns").unwrap(); let isbns = params.get("isbns").unwrap();
let splitisbns : Vec<String> = isbns.split(",").map(|s| format!("ISBN:{}",s.to_string())).collect(); let splitisbns : Vec<String> = isbns.split(",").map(|s| format!("ISBN:{}",s.to_string())).collect();
@@ -275,34 +286,76 @@ async fn create_by_isbn(
let res = reqwest::get(query).await.expect("Unable to request"); let res = reqwest::get(query).await.expect("Unable to request");
let resjson: BooksManyISBN = res.json::<BooksManyISBN>().await.expect("Unable to return value"); let resjson: BooksManyISBN = res.json::<BooksManyISBN>().await.expect("Unable to return value");
for isbnstring in splitisbns.iter() { for isbnstring in splitisbns.iter() {
let mut cover = resjson.clone().book_items.get(isbnstring).unwrap().cover.clone().unwrap().medium.clone(); let bookfound = resjson.book_items.get(isbnstring);
if !resjson.book_items.get(isbnstring).unwrap().cover.is_none() { if !bookfound.clone().is_none() {
let img_bytes = reqwest::get(cover.clone()).await.unwrap().bytes().await.unwrap(); let mut cover_id = "".to_string();
if !bookfound.unwrap().cover.is_none() {
let coverurl = bookfound.clone().unwrap().cover.clone().unwrap().medium.clone()
let img_bytes = reqwest::get(coverurl.clone()).await.unwrap().bytes().await.unwrap();
let image = image::load_from_memory(&img_bytes).unwrap(); let image = image::load_from_memory(&img_bytes).unwrap();
let temp_cover = cover.clone(); let temp_cover = coverurl.clone();
let img_id = temp_cover.split("/").last().unwrap(); let img_id = temp_cover.split("/").last().unwrap();
image.save(format!("../images/{}",img_id)).expect("Failed to save image"); image.save(format!("{}/{}",images_dir,img_id)).expect("Failed to save image");
cover = img_id.to_string(); cover_id = img_id.to_string();
} }
let mut goodread_id = "".to_string();
if !bookfound.unwrap().identifiers.is_none() {
let goodread_identifier = bookfound.clone().unwrap().identifiers.clone().unwrap().goodreads
if !goodread_identifier.is_none() {
goodread_id = goodread_identifier.unwrap().get(0).unwrap().to_string()
}
}
let mut authors_name = Some(vec!["".to_string()]);
//if !bookfound.unwrap().authors.is_none() {
authors_name = Some(bookfound.clone().unwrap().authors.iter().map(|s| s.name.clone()).collect());
//} else {
// authors_name = None
//}
let mut persons = Some(vec!["".to_string()]);
if !bookfound.unwrap().subject_people.is_none() {
persons = Some(bookfound.clone().unwrap().subject_people.clone().unwrap().iter().map(|s| s.name.clone()).collect());
} else {
persons = None;
}
let mut places = Some(vec!["".to_string()]);
if !bookfound.unwrap().subject_places.is_none() {
places = Some(bookfound.clone().unwrap().subject_places.clone().unwrap().iter().map(|s| s.name.clone()).collect());
} else {
places = None;
}
let mut subjects = Some(vec!["".to_string()]);
if !bookfound.unwrap().subjects.is_none() {
subjects = Some(bookfound.clone().unwrap().subjects.clone().unwrap().iter().map(|s| s.name.clone()).collect());
} else {
subjects = None;
}
let mut times = Some(vec!["".to_string()]);
if !bookfound.unwrap().subject_times.is_none() {
times = Some(bookfound.clone().unwrap().subject_times.clone().unwrap().iter().map(|s| s.name.clone()).collect());
} else {
times = None;
}
let doc_sent = BookUI{ let doc_sent = BookUI{
id: 1, id: 1,
open_library_key: Some(resjson.clone().book_items.get(isbnstring).unwrap().key.clone()), open_library_key: Some(bookfound.clone().unwrap().key.clone()),
title: resjson.clone().book_items.get(isbnstring).unwrap().title.clone(), title: bookfound.clone().unwrap().title.clone(),
edition_count: None, edition_count: None,
first_publish_year: None, first_publish_year: None,
median_page_count: 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()), goodread_id: Some(goodread_id),
description: None, description: None,
cover: Some(cover.clone()), cover: Some(cover_id.clone()),
location: None, location: None,
time_added: None, time_added: None,
rating: None, rating: None,
comments: Some("From openlibrary".to_string()), comments: Some("From openlibrary".to_string()),
author_name: Some(resjson.clone().book_items.get(isbnstring).unwrap().authors.iter().map(|s| s.name.clone()).collect()), author_name: authors_name,
person: Some(resjson.clone().book_items.get(isbnstring).unwrap().subject_people.clone().unwrap().iter().map(|s| s.name.clone()).collect()), person: persons,
place: Some(resjson.clone().book_items.get(isbnstring).unwrap().subject_places.clone().unwrap().iter().map(|s| s.name.clone()).collect()), place: places,
subject: Some(resjson.clone().book_items.get(isbnstring).unwrap().subjects.clone().unwrap().iter().map(|s| s.name.clone()).collect()), subject: subjects,
time: Some(resjson.clone().book_items.get(isbnstring).unwrap().subject_times.clone().unwrap().iter().map(|s| s.name.clone()).collect()), time: times,
isbn: Some(vec![isbnstring.split("ISBN:").last().unwrap().to_string()]) isbn: Some(vec![isbnstring.split("ISBN:").last().unwrap().to_string()])
}; };
let book: book::Model = book::Model{ let book: book::Model = book::Model{
@@ -314,7 +367,7 @@ async fn create_by_isbn(
goodread_id: doc_sent.goodread_id.to_owned(), goodread_id: doc_sent.goodread_id.to_owned(),
description: doc_sent.description.to_owned(), description: doc_sent.description.to_owned(),
comments: doc_sent.comments.to_owned(), comments: doc_sent.comments.to_owned(),
cover: Some(cover.to_owned()), cover: Some(cover_id.to_owned()),
rating: doc_sent.rating.to_owned(), rating: doc_sent.rating.to_owned(),
time_added: doc_sent.time_added.to_owned(), time_added: doc_sent.time_added.to_owned(),
id: 1, id: 1,
@@ -412,6 +465,7 @@ async fn create_by_isbn(
}; };
booksman_search::create_or_update_book(book_meili, meili_client).await; booksman_search::create_or_update_book(book_meili, meili_client).await;
} }
}
return "Done"; return "Done";
} }
@@ -431,6 +485,10 @@ async fn search_openlibrary(
let mut vec = Vec::with_capacity(12); let mut vec = Vec::with_capacity(12);
for i in 0..12 as usize { for i in 0..12 as usize {
let doc = resjson.docs[i].clone(); let doc = resjson.docs[i].clone();
let mut goodread = "".to_string();
if doc.id_goodreads.is_some() {
goodread = doc.id_goodreads.unwrap().get(0).unwrap().to_string();
}
vec.push( vec.push(
BookUI{ BookUI{
id: -((i+1) as i32), id: -((i+1) as i32),
@@ -439,7 +497,7 @@ let mut vec = Vec::with_capacity(12);
edition_count: doc.edition_count, edition_count: doc.edition_count,
first_publish_year: doc.first_publish_year, first_publish_year: doc.first_publish_year,
median_page_count: doc.number_of_pages_median, median_page_count: doc.number_of_pages_median,
goodread_id: doc.goodread_id, goodread_id: Some(goodread),
description: doc.description, description: doc.description,
cover: doc.cover_url, cover: doc.cover_url,
location: None, location: None,
@@ -474,6 +532,11 @@ async fn list_book(
Extension(ref conn): Extension<DatabaseConnection>, Extension(ref conn): Extension<DatabaseConnection>,
axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>, axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let page: usize = params.get("page").unwrap().parse().unwrap(); let page: usize = params.get("page").unwrap().parse().unwrap();
let books = QueryCore::find_books_plus_meta_in_page(conn,page,5) let books = QueryCore::find_books_plus_meta_in_page(conn,page,5)
@@ -485,7 +548,9 @@ let mut resbooks: Vec<BookUI> = Vec::with_capacity(5);
for bookandmeta in books.0.into_iter() { for bookandmeta in books.0.into_iter() {
let mut cover = bookandmeta.clone().book.cover; let mut cover = bookandmeta.clone().book.cover;
if !cover.is_none() { if !cover.is_none() {
cover = Some(format!("http://localhost:8081/images/{}",cover.unwrap())); cover = Some(format!("{}/images/{}",backend_url,cover.unwrap()));
} else {
cover = Some(format!("{}/images/placeholder.jpg",backend_url));
} }
let bookui = BookUI{ let bookui = BookUI{
id: bookandmeta.clone().book.id, id: bookandmeta.clone().book.id,
@@ -526,6 +591,9 @@ async fn list_search_book(
) -> impl IntoResponse { ) -> impl IntoResponse {
let page: usize = params.get("page").unwrap().parse().unwrap(); let page: usize = params.get("page").unwrap().parse().unwrap();
let search = params.get("search").unwrap(); let search = params.get("search").unwrap();
dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let books = booksman_search::search_book(search, page,meili_client) let books = booksman_search::search_book(search, page,meili_client)
.await .await
@@ -536,7 +604,9 @@ let mut resbooks: Vec<BookUI> = Vec::with_capacity(24);
for bookmeili in books.into_iter() { for bookmeili in books.into_iter() {
let mut cover = bookmeili.clone().cover; let mut cover = bookmeili.clone().cover;
if cover!="".to_string() { if cover!="".to_string() {
cover = format!("http://localhost:8081/images/{}",cover); cover = format!("{}/images/{}",backend_url,cover);
} else {
cover = format!("{}/images/placeholder.jpg",backend_url);
} }
let bookui = BookUI{ let bookui = BookUI{
id: bookmeili.id, id: bookmeili.id,
@@ -577,6 +647,10 @@ async fn create_book(
Json(doc_sent): Json<BookUI>, Json(doc_sent): Json<BookUI>,
) -> impl IntoResponse { ) -> impl IntoResponse {
println!("Creating book"); println!("Creating book");
dotenvy::dotenv().ok();
let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file");
let mut cover = doc_sent.cover.clone(); let mut cover = doc_sent.cover.clone();
if !doc_sent.cover.is_none() { if !doc_sent.cover.is_none() {
let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap(); let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap();
@@ -585,7 +659,7 @@ async fn create_book(
let image = image::load_from_memory(&img_bytes).unwrap(); let image = image::load_from_memory(&img_bytes).unwrap();
let temp_cover = doc_sent.cover.clone().unwrap(); let temp_cover = doc_sent.cover.clone().unwrap();
let img_id = temp_cover.split("/").last().unwrap(); let img_id = temp_cover.split("/").last().unwrap();
image.save(format!("../images/{}",img_id)).expect("Failed to save image"); image.save(format!("{}/{}",images_dir,img_id)).expect("Failed to save image");
cover = Some(img_id.to_string()); cover = Some(img_id.to_string());
} }
let book: book::Model = book::Model{ let book: book::Model = book::Model{
@@ -707,6 +781,10 @@ async fn update_book(
Json(doc_sent): Json<BookUI>, Json(doc_sent): Json<BookUI>,
) -> impl IntoResponse { ) -> impl IntoResponse {
println!("Updating book"); println!("Updating book");
dotenvy::dotenv().ok();
let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file");
let mut cover = doc_sent.cover.clone(); let mut cover = doc_sent.cover.clone();
if !doc_sent.cover.is_none() { if !doc_sent.cover.is_none() {
let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap(); let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap();
@@ -715,7 +793,7 @@ if !doc_sent.cover.is_none() {
let image = image::load_from_memory(&img_bytes).unwrap(); let image = image::load_from_memory(&img_bytes).unwrap();
let temp_cover = doc_sent.cover.clone().unwrap(); let temp_cover = doc_sent.cover.clone().unwrap();
let img_id = temp_cover.split("/").last().unwrap(); let img_id = temp_cover.split("/").last().unwrap();
image.save(format!("../images/{}",img_id)).expect("Failed to save image"); image.save(format!("{}/{}",images_dir,img_id)).expect("Failed to save image");
cover = Some(img_id.to_string()); cover = Some(img_id.to_string());
} }
let book: book::Model = book::Model{ let book: book::Model = book::Model{

7
frontend/Cargo.lock generated
View File

@@ -72,6 +72,12 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "dotenvy"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.9.0" version = "0.9.0"
@@ -91,6 +97,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"console_log", "console_log",
"dotenvy",
"env_logger", "env_logger",
"gloo-net 0.2.4", "gloo-net 0.2.4",
"log", "log",

View File

@@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
dotenvy = "0.15.0"
env_logger = "0.9.0" env_logger = "0.9.0"
gloo-net = "^0.2" gloo-net = "^0.2"
#gloo-utils = {version = "0.1.5", features =["serde"]} #gloo-utils = {version = "0.1.5", features =["serde"]}

View File

@@ -1,5 +1,7 @@
use log::info; use log::info;
//use log::Level; //use log::Level;
use std::env;
use reqwasm::http::Request; use reqwasm::http::Request;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json; use serde_json;
@@ -69,41 +71,63 @@ enum AppRoutes {
} }
async fn fetch_books(search: String) -> Result<Vec<BookUI>, reqwasm::Error> { async fn fetch_books(search: String) -> Result<Vec<BookUI>, reqwasm::Error> {
let url = format!("http://localhost:8081/api/search_openlibrary?search={}", search); dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let url = format!("{}/api/search_openlibrary?search={}", backend_url, search);
let resp = Request::get(&url).send().await?; let resp = Request::get(&url).send().await?;
println!("Fetching books\n"); println!("Fetching books\n");
let body = resp.json::<Vec<BookUI>>().await?; let body = resp.json::<Vec<BookUI>>().await?;
Ok(body) Ok(body)
} }
async fn search_books(search: String, page: u32) -> Result<Vec<BookUI>, reqwasm::Error> { async fn search_books(search: String, page: u32) -> Result<PaginatedBookUIList, reqwasm::Error> {
let url = format!("http://localhost:8081/api/list_search?search={}&page={}", search, page); dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let url = format!("{}/api/list_search?search={}&page={}",backend_url, search, page);
let resp = Request::get(&url).send().await?; let resp = Request::get(&url).send().await?;
println!("Fetching books\n"); println!("Fetching books\n");
let body = resp.json::<Vec<PaginatedBookUIList>().await?; let body = resp.json::<PaginatedBookUIList>().await?;
Ok(body) Ok(body)
} }
async fn add_book(record: BookUI) -> Result<reqwasm::http::Response, reqwasm::Error> { async fn add_book(record: BookUI) -> Result<reqwasm::http::Response, reqwasm::Error> {
let url = format!("http://localhost:8081/api/create"); dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let url = format!("{}/api/create", backend_url);
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?; 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?;
Ok(resp) Ok(resp)
} }
async fn add_books_isbns(isbns: String) -> Result<reqwasm::http::Response, reqwasm::Error> {
dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let url = format!("{}/api/create_by_isbn?isbns={}", backend_url, isbns);
let resp = Request::get(&url).send().await?;
println!("Adding multiple books\n");
Ok(resp)
}
async fn update_book(record: BookUI) -> Result<reqwasm::http::Response, reqwasm::Error> { async fn update_book(record: BookUI) -> Result<reqwasm::http::Response, reqwasm::Error> {
let url = format!("http://localhost:8081/api/update"); dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let url = format!("{}/api/update", backend_url);
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?; 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?;
Ok(resp) Ok(resp)
} }
async fn delete_book(id: i32) -> Result<reqwasm::http::Response, reqwasm::Error> { async fn delete_book(id: i32) -> Result<reqwasm::http::Response, reqwasm::Error> {
let url = format!("http://localhost:8081/api/delete/{}", id); dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let url = format!("{}/api/delete/{}", backend_url, id);
let resp = Request::get(&url).send().await?; let resp = Request::get(&url).send().await?;
Ok(resp) Ok(resp)
} }
async fn list_books(page: u32) -> Result<PaginatedBookUIList, reqwasm::Error> { async fn list_books(page: u32) -> Result<PaginatedBookUIList, reqwasm::Error> {
let url = format!("http://localhost:8081/api/list?page={}",page); dotenvy::dotenv().ok();
let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file");
let url = format!("{}/api/list?page={}", backend_url, page);
let resp = Request::get(&url).send().await?; let resp = Request::get(&url).send().await?;
println!("Fetching books\n"); println!("Fetching books\n");
let body = resp.json::<PaginatedBookUIList>().await?; let body = resp.json::<PaginatedBookUIList>().await?;
@@ -116,6 +140,13 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
let value = create_signal(cx, String::new()); let value = create_signal(cx, String::new());
let input_ref = create_node_ref(cx); let input_ref = create_node_ref(cx);
let value2 = create_signal(cx, String::new());
let input_ref2 = create_node_ref(cx);
let value3 = create_signal(cx, String::new());
let input_ref3 = create_node_ref(cx);
let handle_submit = |event: Event| { let handle_submit = |event: Event| {
let event: KeyboardEvent = event.unchecked_into(); let event: KeyboardEvent = event.unchecked_into();
@@ -137,26 +168,53 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
} }
} }
}; };
let click_listall = |_| {
app_state.openlibrary.set(false);
app_state.internalsearch.set(false);
};
let click_searchall = |_| { let handle_submit_seachall = |event: Event| {
let event: KeyboardEvent = event.unchecked_into();
let mut task = value.get().as_ref().clone(); if event.key() == "Enter" {
let mut task = value2.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);
info!("Fetching search\n");
}
}
};
let click_listall = |_| {
app_state.openlibrary.set(false);
app_state.internalsearch.set(false);
};
let click_searchOL = |_| {
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(true);
app_state.internalsearch.set(false);
}
};
let click_searchall = |_| {
let mut task = value2.get().as_ref().clone();
task = task.trim().to_string(); task = task.trim().to_string();
if !task.is_empty() { if !task.is_empty() {
app_state.search.set(task); app_state.search.set(task);
app_state.openlibrary.set(false); app_state.openlibrary.set(false);
app_state.internalsearch.set(true); app_state.internalsearch.set(true);
value.set("".to_string());
input_ref
.get::<DomNode>()
.unchecked_into::<HtmlInputElement>()
.set_value("");
} }
}; };
@@ -165,6 +223,35 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
app_state.addingbook.set(BookUI::default()); app_state.addingbook.set(BookUI::default());
}; };
let handle_submit_addbulk = |event: Event| {
let event: KeyboardEvent = event.unchecked_into();
if event.key() == "Enter" {
let mut task = value3.get().as_ref().clone();
task = task.trim().to_string();
if !task.is_empty() {
spawn_local(async move {
let temp = add_books_isbns(task).await.unwrap();
});
}
}
};
let click_addbulk = |_| {
let mut task = value3.get().as_ref().clone();
task = task.trim().to_string();
if !task.is_empty() {
spawn_local(async move {
let temp = add_books_isbns(task).await.unwrap();
});
}
};
view! { cx, view! { cx,
header(class="header") { header(class="header") {
h1 { "Search OpenLibrary" } h1 { "Search OpenLibrary" }
@@ -174,9 +261,27 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
bind:value=value, bind:value=value,
on:keyup=handle_submit, on:keyup=handle_submit,
) )
button(on:click=click_searchOL) { "Search internal" }
button(on:click=click_listall) { "List All DB" } button(on:click=click_listall) { "List All DB" }
input(ref=input_ref2,
class="new-todo",
placeholder="Search internal DB",
bind:value=value2,
on:keyup=handle_submit_seachall,
)
button(on:click=click_searchall) { "Search internal" } button(on:click=click_searchall) { "Search internal" }
button(on:click=click_addbook) { "+ Add New" } button(on:click=click_addbook) { "+ Add New" }
input(ref=input_ref3,
class="new-todo",
placeholder="Add bulk ISBNs",
bind:value=value3,
on:keyup=handle_submit_addbulk,
)
button(on:click=click_addbulk) { "Add bulk ISBNs" }
} }
} }
} }
@@ -193,19 +298,18 @@ async fn ListDB<G: Html>(cx: Scope<'_>) -> View<G> {
if *app_state.internalsearch.get() == false { if *app_state.internalsearch.get() == false {
spawn_local(async move { spawn_local(async move {
app_state.books.set( app_state.books.set(
list_books(app_state.pagenum.get()) list_books(*app_state.pagenum.get())
.await .await
.unwrap().books, .unwrap().books
) )
}); });
} else { } else {
spawn_local(async move { spawn_local(async move {
app_state.books.set( app_state.books.set(
search_books(app_state.search.get().to_string(), app_state.pagenum.get()) search_books(app_state.search.get().to_string(), *app_state.pagenum.get())
.await .await
.unwrap(), .unwrap().books)
) })
}
} }
} }
@@ -242,7 +346,7 @@ pub fn BookDB<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
let bookupdate = bookitem.bookitem.clone(); let bookupdate = bookitem.bookitem.clone();
let bookdisplay = bookitem.bookitem.clone(); let bookdisplay = bookitem.bookitem.clone();
let coverurl = book.cover.clone().unwrap_or("http://localhost:8081/images/placeholder.jpg".to_string()); let coverurl = bookdisplay.cover.clone().unwrap_or("http://localhost:8081/images/placeholder.jpg".to_string());
let handle_delete = move |_| { let handle_delete = move |_| {
spawn_local(async move { spawn_local(async move {
let temp = delete_book(bookdelete.id).await.unwrap(); let temp = delete_book(bookdelete.id).await.unwrap();
@@ -266,7 +370,7 @@ pub fn BookDB<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
div(class="card"){ div(class="card"){
img(src=coverurl,width="100") img(src=coverurl,width="100")
(format!("{:?}",book)) (format!("{:?}",bookdisplay))
button(class="delete", on:click=handle_delete){ "-" } button(class="delete", on:click=handle_delete){ "-" }
button(class="update", on:click=handle_update){ "=" } button(class="update", on:click=handle_update){ "=" }
button(class="info", on:click=handle_display){ "+" } button(class="info", on:click=handle_display){ "+" }
@@ -474,20 +578,54 @@ async fn SelectedUI<G: Html>(cx: Scope<'_>) -> View<G> {
let app_state = use_context::<AppState>(cx); let app_state = use_context::<AppState>(cx);
let displ_book = create_signal(cx, (*app_state.displayingbook.get()).clone()); let displ_book = create_signal(cx, (*app_state.displayingbook.get()).clone());
let handle_update = move |_| { let handle_close = move |_| {
app_state.displaying.set(false); app_state.displaying.set(false);
}; };
view! {cx, view! {cx,
p{
(if *app_state.displaying.get() == true { (if *app_state.displaying.get() == true {
p { view!{ cx,
div(class="select-book"){ div(class="select-book"){
img(src=coverurl,width="100") img(src=coverurl,width="100")
(format!("{:?}",book)) (format!("{:?}",book))
button(class="close", on:click=handle_close){ "CLOSE" } button(class="close", on:click=handle_close){ "CLOSE" }
} }
} }
} } else {
view!{cx,""}
}) })
}
}
}
#[component]
async fn PageBar<G: Html>(cx: Scope<'_>) -> View<G> {
let app_state = use_context::<AppState>(cx);
let handle_add = move |_| {
app_state.pagenum.set(*app_state.pagenum.get()+1);
};
let handle_sub = move |_| {
app_state.pagenum.set(*app_state.pagenum.get()-1);
};
view! {cx,
p {
(if *app_state.openlibrary.get() == true || *app_state.internalsearch.get() == true {
view!{ cx,
button(class="page", on:click=handle_sub){ "Page -" }
(format!("{:?}",app_state.pagenum.get()))
button(class="page", on:click=handle_add){ "Page +" }
}
}else {
view!{cx,""}
})
}
}
} }
@@ -514,6 +652,7 @@ fn App<G: Html>(cx: Scope) -> View<G> {
SelectedUI{} SelectedUI{}
ListOL {} ListOL {}
ListDB{} ListDB{}
PageBar{}
} }
} }
} }