v0.12-searchP2

This commit is contained in:
2022-11-21 00:13:47 +05:30
parent 875b826f0b
commit f297d8e4e0
6 changed files with 510 additions and 88 deletions

View File

@@ -1,16 +1,11 @@
TODO TODO
Pagination Pagination
Update Update
Search - meilisearch Search - meilisearch
GoodreadID, GoogleID, Description, Excerpt
Publishers
ISBN - mass fill - different format ISBN - mass fill - different format
GoodreadID, GoogleID, Description, Excerpt, Publishers
GoogleAPI fallback GoogleAPI fallback

1
backend/Cargo.lock generated
View File

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

View File

@@ -15,6 +15,7 @@ dotenvy = "0.15.0"
image = "0.24" image = "0.24"
error-chain = "0.12.4" error-chain = "0.12.4"
log = "^0.4" log = "^0.4"
meilisearch-sdk = "0.20.1"
reqwest = {version = "0.11.11", features = ["json"]} reqwest = {version = "0.11.11", features = ["json"]}
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0" serde_json = "^1.0"

View File

@@ -18,12 +18,15 @@ use tower_http::services::ServeDir;
use tower_http::cors::{Any,CorsLayer}; use tower_http::cors::{Any,CorsLayer};
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
use booksman_search::BookMeili;
use booksman_search;
use booksman_orm::{ use booksman_orm::{
sea_orm::{Database, DatabaseConnection}, sea_orm::{Database, DatabaseConnection},
Mutation as MutationCore, Query as QueryCore, 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 ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn};
use std::env; use std::env;
@@ -91,6 +94,48 @@ struct BookPageOL {
struct DescriptionOL { struct DescriptionOL {
value: Option<String>, value: Option<String>,
} }
/*
#[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<String, BookISBNItem>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BookISBNItem {
pub key: String,
pub title: String,
pub authors: Vec<Subject>,
pub identifiers: Option<Identifiers>,
pub publish_date: Option<String>,
pub subjects: Option<Vec<Subject>>,
pub subject_places: Option<Vec<Subject>>,
pub subject_people: Option<Vec<Subject>>,
pub subject_times: Option<Vec<Subject>>,
pub cover: Option<Cover>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Identifiers {
pub goodreads: Option<Vec<String>>,
}
#[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 { impl Books {
@@ -99,7 +144,7 @@ impl Books {
match book.cover_i { match book.cover_i {
Some(cover_i) => { Some(cover_i) => {
book.cover_url = Some(format!( book.cover_url = Some(format!(
"https://covers.openlibrary.org/b/id/{}-L.jpg", "https://covers.openlibrary.org/b/id/{}-M.jpg",
cover_i cover_i
)) ))
} }
@@ -107,6 +152,7 @@ impl Books {
} }
} }
} }
/*
async fn set_all_descriptions(&mut self) { async fn set_all_descriptions(&mut self) {
for book in self.docs.iter_mut() { for book in self.docs.iter_mut() {
let query = format!("https://openlibrary.org/{}.json", book.key); let query = format!("https://openlibrary.org/{}.json", book.key);
@@ -124,6 +170,7 @@ impl Books {
} }
} }
} }
*/
} }
async fn handle_error(_err: io::Error) -> impl IntoResponse { async fn handle_error(_err: io::Error) -> impl IntoResponse {
@@ -165,23 +212,30 @@ pub async fn main() {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
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_key = env::var("MEILI_KEY").expect("MEILI_KEY is not set in .env file");
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(); //Migrator::up(&conn, None).await.unwrap();
let meili_client = Client::new(meili_url, meili_key);
let app = Router::new() let app = Router::new()
.route("/api/search_openlibrary", get(search_openlibrary)) .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/delete/:id", get(delete_book))
.route("/api/list", get(list_book)) .route("/api/list", get(list_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")).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))
.layer( .layer(Extension(meili_client))
.layer(
// see https://docs.rs/tower-http/latest/tower_http/cors/index.html // see https://docs.rs/tower-http/latest/tower_http/cors/index.html
// for more details // for more details
// //
@@ -207,6 +261,161 @@ pub async fn main() {
.expect("Unable to start server"); .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<DatabaseConnection>,
Extension(ref meili_client): Extension<Client>,
axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse {
print!("Get items with query params: {:?}\n", params);
let isbns = params.get("isbns").unwrap();
let splitisbns : Vec<String> = 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::<BooksManyISBN>().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( async fn search_openlibrary(
axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>, axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -223,27 +432,27 @@ 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();
vec.push( vec.push(
BookUI{ BookUI{
id: -((i+1) as i32), id: -((i+1) as i32),
open_library_key: Some(doc.key), open_library_key: Some(doc.key),
title: doc.title, title: doc.title,
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: doc.goodread_id,
description: doc.description, description: doc.description,
cover: doc.cover_url, cover: doc.cover_url,
location: None, location: None,
time_added: None, time_added: None,
rating: None, rating: None,
comments: None, comments: Some("From openlibrary".to_string()),
author_name: doc.author_name, author_name: doc.author_name,
person: doc.person, person: doc.person,
place: doc.place, place: doc.place,
subject: doc.subject, subject: doc.subject,
time: doc.time, time: doc.time,
isbn: doc.isbn isbn: doc.isbn
} }
); );
} }
return Json(vec); return Json(vec);
@@ -251,11 +460,13 @@ isbn: doc.isbn
async fn delete_book( async fn delete_book(
Extension(ref conn): Extension<DatabaseConnection>, Extension(ref conn): Extension<DatabaseConnection>,
Extension(ref meili_client): Extension<Client>,
Path(id): Path<i32>, Path(id): Path<i32>,
) -> impl IntoResponse { ) -> impl IntoResponse {
MutationCore::delete_book(conn, id) MutationCore::delete_book(conn, id)
.await .await
.expect("could not delete book"); .expect("could not delete book");
booksman_search::delete_book(id, meili_client).await;
"success" "success"
} }
@@ -309,23 +520,75 @@ return Json(res);
} }
async fn list_search_book(
Extension(ref meili_client): Extension<Client>,
axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> 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<BookUI> = 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( async fn create_book(
Extension(ref conn): Extension<DatabaseConnection>, Extension(ref conn): Extension<DatabaseConnection>,
Extension(ref meili_client): Extension<Client>,
Json(doc_sent): Json<BookUI>, Json(doc_sent): Json<BookUI>,
) -> impl IntoResponse { ) -> impl IntoResponse {
println!("Creating book"); println!("Creating book");
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();
//.expect("Could not fetch image"); //.expect("Could not fetch image");
//let img_bytes = img_resp.unwrap().bytes(); //let img_bytes = img_resp.unwrap().bytes();
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/{}",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{
open_library_key: doc_sent.open_library_key.to_owned(), open_library_key: doc_sent.open_library_key.to_owned(),
title: (doc_sent.title.to_owned()), title: (doc_sent.title.to_owned()),
edition_count: doc_sent.edition_count.to_owned(), edition_count: doc_sent.edition_count.to_owned(),
@@ -409,6 +672,28 @@ let book: book::Model = book::Model{
.expect("could not create book"); .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" "success"
@@ -418,6 +703,7 @@ let book: book::Model = book::Model{
async fn update_book( async fn update_book(
Extension(ref conn): Extension<DatabaseConnection>, Extension(ref conn): Extension<DatabaseConnection>,
Extension(ref meili_client): Extension<Client>,
Json(doc_sent): Json<BookUI>, Json(doc_sent): Json<BookUI>,
) -> impl IntoResponse { ) -> impl IntoResponse {
println!("Updating book"); println!("Updating book");
@@ -447,7 +733,7 @@ let book: book::Model = book::Model{
id: doc_sent.id.to_owned(), id: doc_sent.id.to_owned(),
location: doc_sent.location.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 .await
.expect("could not update book"); .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" "success"
} }

View File

@@ -1,30 +1,59 @@
use meilisearch_sdk::{client::*, search::*}; use meilisearch_sdk::client::*;
use meilisearch_sdk::search::*;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
#[derive(Deserialize, Serialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug)] pub struct BookMeili {
struct Movie { pub id: i32,
id: usize, pub open_library_key: String,
title: String, pub title: String,
genres: Vec<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<String>,
pub person: Vec<String>,
pub place: Vec<String>,
pub subject: Vec<String>,
pub time: Vec<String>,
pub isbn: Vec<String>,
} }
#[tokio::main] pub async fn create_or_update_book(book: BookMeili, client: &Client) {
async fn main() {
// Create a client (without sending any request so that can't fail)
let client = Client::new(MEILISEARCH_URL, MEILISEARCH_API_KEY);
// An index is where the documents are stored. // 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. // 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(&[ books.add_or_replace(&[
Movie { id: 1, title: String::from("Carol"), genres: vec!["Romance".to_string(), "Drama".to_string()] }, book
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()] },
], Some("id")).await.unwrap(); ], 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<Vec<BookMeili>, meilisearch_sdk::errors::Error> {
// An index is where the documents are stored.
let books = client.index("books");
let results : SearchResults<BookMeili> = books.search().with_query(search).with_offset((page-1)*24)
.execute::<BookMeili>().await.unwrap();
let formatted_results : Vec<BookMeili> = (results.hits).iter().map(|r| r.result.clone()).collect();
//.iter()s.map(|r| r.formatted_result.unwrap()).collect();
return Ok(formatted_results);
}

View File

@@ -49,6 +49,8 @@ pub struct AppState {
pub books: RcSignal<Vec<BookUI>>, pub books: RcSignal<Vec<BookUI>>,
pub search: RcSignal<String>, pub search: RcSignal<String>,
pub openlibrary: RcSignal<bool>, pub openlibrary: RcSignal<bool>,
pub internalsearch: RcSignal<bool>,
pub pagenum: RcSignal<u32>,
pub adding: RcSignal<bool>, pub adding: RcSignal<bool>,
pub updating: RcSignal<bool>, pub updating: RcSignal<bool>,
pub displaying: RcSignal<bool>, pub displaying: RcSignal<bool>,
@@ -74,6 +76,14 @@ async fn fetch_books(search: String) -> Result<Vec<BookUI>, reqwasm::Error> {
Ok(body) Ok(body)
} }
async fn search_books(search: String, page: u32) -> Result<Vec<BookUI>, 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::<Vec<PaginatedBookUIList>().await?;
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"); 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?; 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<G: Html>(cx: Scope) -> View<G> {
if !task.is_empty() { if !task.is_empty() {
app_state.search.set(task); app_state.search.set(task);
app_state.openlibrary.set(true); app_state.openlibrary.set(true);
app_state.internalsearch.set(false);
info!("Fetching search\n"); info!("Fetching search\n");
value.set("".to_string()); value.set("".to_string());
input_ref input_ref
@@ -125,11 +137,34 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
} }
} }
}; };
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::<DomNode>()
.unchecked_into::<HtmlInputElement>()
.set_value("");
}
};
let click_addbook = |_| { let click_addbook = |_| {
app_state.adding.set(true); app_state.adding.set(true);
app_state.addingbook.set(BookUI::default()); app_state.addingbook.set(BookUI::default());
}; };
view! { cx, view! { cx,
header(class="header") { header(class="header") {
h1 { "Search OpenLibrary" } h1 { "Search OpenLibrary" }
@@ -140,7 +175,8 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
on:keyup=handle_submit, on:keyup=handle_submit,
) )
button(on:click=click_listall) { "List All DB" } 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<G: Html>(cx: Scope<'_>) -> View<G> {
let app_state = use_context::<AppState>(cx); let app_state = use_context::<AppState>(cx);
create_effect(cx, || { create_effect(cx, || {
let app_state = app_state.clone(); let app_state = app_state.clone();
app_state.search.track();
app_state.pagenum.track();
if *app_state.openlibrary.get() == false { if *app_state.openlibrary.get() == false {
spawn_local(async move { if *app_state.internalsearch.get() == false {
app_state.books.set( spawn_local(async move {
list_books(1) app_state.books.set(
.await list_books(app_state.pagenum.get())
.unwrap().books, .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::<Vec<_>>()); let docs = create_memo(cx, || app_state.books.get().iter().cloned().collect::<Vec<_>>());
@@ -187,12 +238,14 @@ async fn ListDB<G: Html>(cx: Scope<'_>) -> View<G> {
#[component(inline_props)] #[component(inline_props)]
pub fn BookDB<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> { pub fn BookDB<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
let app_state = use_context::<AppState>(cx); let app_state = use_context::<AppState>(cx);
let book = bookitem.bookitem.clone();
let bookdelete = 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 coverurl = book.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(book.id).await.unwrap(); let temp = delete_book(bookdelete.id).await.unwrap();
println!("{}",temp.status()); println!("{}",temp.status());
}); });
}; };
@@ -200,9 +253,14 @@ pub fn BookDB<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
let handle_update = move |_| { let handle_update = move |_| {
app_state.adding.set(false); app_state.adding.set(false);
app_state.updating.set(true); 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, view! { cx,
div(class="column"){ div(class="column"){
div(class="card"){ div(class="card"){
@@ -211,6 +269,8 @@ pub fn BookDB<G: Html>(cx: Scope, bookitem: BookUIProp) -> View<G> {
(format!("{:?}",book)) (format!("{:?}",book))
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){ "+" }
} }
} }
@@ -228,13 +288,16 @@ async fn ListOL<G: Html>(cx: Scope<'_>) -> View<G> {
//); //);
let app_state = app_state.clone(); let app_state = app_state.clone();
app_state.search.track(); app_state.search.track();
spawn_local(async move { spawn_local(async move {
if *app_state.search.get() != "" { if *app_state.search.get() != "" {
app_state.books.set( if *app_state.openlibrary.get() == true {
fetch_books(app_state.search.get().to_string()) app_state.books.set(
.await fetch_books(app_state.search.get().to_string() )
.unwrap(), .await
) .unwrap(),
)
}
} }
}); });
}); });
@@ -406,12 +469,36 @@ info!("Adding book");
} }
#[component]
async fn SelectedUI<G: Html>(cx: Scope<'_>) -> View<G> {
let app_state = use_context::<AppState>(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] #[component]
fn App<G: Html>(cx: Scope) -> View<G> { fn App<G: Html>(cx: Scope) -> View<G> {
let app_state = AppState { let app_state = AppState {
books: create_rc_signal(Vec::new()), books: create_rc_signal(Vec::new()),
search: create_rc_signal(String::default()), search: create_rc_signal(String::default()),
openlibrary: create_rc_signal(bool::default()), openlibrary: create_rc_signal(bool::default()),
internalsearch: create_rc_signal(bool::default()),
pagenum: create_rc_signal(1),
adding: create_rc_signal(bool::default()), adding: create_rc_signal(bool::default()),
updating: create_rc_signal(bool::default()), updating: create_rc_signal(bool::default()),
addingbook: create_rc_signal(BookUI::default()), addingbook: create_rc_signal(BookUI::default()),
@@ -424,6 +511,7 @@ fn App<G: Html>(cx: Scope) -> View<G> {
div { div {
Header {} Header {}
AddingUI{} AddingUI{}
SelectedUI{}
ListOL {} ListOL {}
ListDB{} ListDB{}
} }