use axum::{ extract::{Extension, Path}, http::{HeaderValue, Method, StatusCode}, response::IntoResponse, routing::{get, get_service, post}, Json, Router, }; use booksman_orm::{ sea_orm::{Database, DatabaseConnection}, Mutation as MutationCore, Query as QueryCore, // BookAndMeta, }; use booksman_search; use booksman_search::BookMeili; use clap::Parser; use image; use meilisearch_sdk::client::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::io; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::str::FromStr; use tower::ServiceBuilder; use tower_http::cors::{Any, CorsLayer}; use tower_http::services::ServeDir; use tower_http::trace::TraceLayer; //use itertools::Itertools; use ::entity::entities::{ book, book_author, book_isbn, book_person, book_place, book_subject, book_time, user, }; use axum_login::{ axum_sessions::{async_session::MemoryStore as SessionMemoryStore, SessionLayer}, AuthLayer, RequireAuthorizationLayer, }; use chrono::Local; use migration::{Migrator, MigratorTrait}; use rand::Rng; use std::env; #[derive(Deserialize, Serialize, Debug, Clone)] struct BookUI { id: i32, open_library_key: Option, title: String, edition_count: Option, first_publish_year: Option, median_page_count: Option, goodread_id: Option, description: Option, cover: Option, location: Option, time_added: Option, rating: Option, comments: Option, author_name: Option>, person: Option>, place: Option>, subject: Option>, time: Option>, isbn: Option>, } #[derive(Deserialize, Serialize, Debug, Clone)] struct Docs { key: String, title: String, edition_count: Option, first_publish_year: Option, number_of_pages_median: Option, id_goodreads: Option>, description: Option, isbn: Option>, cover_i: Option, cover_url: Option, author_name: Option>, person: Option>, place: Option>, subject: Option>, time: Option>, } #[derive(Deserialize, Serialize, Debug)] struct Books { num_found: u32, docs: Vec, } #[derive(Deserialize, Serialize, Debug)] struct PaginatedBookUIList { num_pages: u32, books: Vec, } #[derive(Deserialize, Serialize, Debug)] struct BookPageOL { description: Option, } #[derive(Deserialize, Serialize, Debug, Clone)] struct DescriptionOLValue { value: String, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] enum DescriptionOL { DescriptionValueString(DescriptionOLValue), 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 { fn set_all_cover_urls(&mut self) { for book in self.docs.iter_mut() { match book.cover_i { Some(cover_i) => { book.cover_url = Some(format!( "https://covers.openlibrary.org/b/id/{}-M.jpg", cover_i )) } None => (), } } } } impl BookUI { async fn set_descriptions(&mut self) { if self.open_library_key.is_some() { if !(self.open_library_key.as_ref().unwrap().is_empty()) { let query = format!( "https://openlibrary.org/{}.json", self.open_library_key.as_ref().unwrap().clone() ); let res = reqwest::get(query).await.expect("Unable to request"); let resjson = res .json::() .await .expect("Unable to return value"); let description = resjson.description; if !description.is_none() { if let DescriptionOL::DescriptionString(desc_string) = description.clone().unwrap() { self.description = Some(desc_string); } if let DescriptionOL::DescriptionValueString(desc_val) = description.clone().unwrap() { self.description = Some(desc_val.value); } } } } } } async fn handle_error(_err: io::Error) -> impl IntoResponse { (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong...") } // Setup the command line interface with clap. #[derive(Parser, Debug)] #[clap(name = "server", about = "A server for our wasm project!")] struct Opt { /// set the log level #[clap(short = 'l', long = "log", default_value = "debug")] log_level: String, /// set the listen addr #[clap(short = 'a', long = "addr", default_value = "0.0.0.0")] addr: String, /// set the listen port #[clap(short = 'p', long = "port", default_value = "8081")] port: u16, /// set the directory where static files are to be found #[clap(long = "static-dir", default_value = "../dist")] static_dir: String, } type AuthContext = axum_login::extractors::AuthContext; #[tokio::main] pub async fn main() { let opt = Opt::parse(); // Setup logging & RUST_LOG from args if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", format!("{},hyper=info,mio=info", opt.log_level)) } // enable console logging tracing_subscriber::fmt::init(); dotenvy::dotenv().ok(); 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 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 secret = rand::thread_rng().gen::<[u8; 64]>(); let conn = Database::connect(db_url) .await .expect("Database connection failed"); // Apply all pending migrations Migrator::up(&conn, None).await.unwrap(); let session_store = SessionMemoryStore::new(); let session_layer = SessionLayer::new(session_store, &secret).with_secure(false); let user_store = booksman_orm::AxumUserStore::new(&conn); let auth_layer: AuthLayer = AuthLayer::new(user_store, &secret); let meili_client = Client::new(meili_url, meili_key); let app = Router::new() .route("/api/create_by_isbn", get(create_by_isbn)) .route("/api/create", post(create_book)) .route("/api/update", post(update_book)) .route("/api/delete/:id", get(delete_book)) .route("/api/authentication_check", get(authentication_check)) .route_layer(RequireAuthorizationLayer::::login()) .route("/api/list", get(list_book)) .route("/api/list_search", get(list_search_book)) .route("/api/list_users", get(list_users)) .route("/api/search_openlibrary", get(search_openlibrary)) .route("/api/login", post(login_handler)) .route("/api/register", post(register_handler)) .route("/api/logout", post(logout_handler)) .nest_service( "/images", get_service(ServeDir::new(images_dir)).handle_error(handle_error), ) .nest_service( "/assets", get_service(ServeDir::new(opt.static_dir)).handle_error(handle_error), ) // .merge(SpaRouter::new("/assets", opt.static_dir)) .layer(auth_layer) .layer(session_layer) .layer(ServiceBuilder::new().layer(TraceLayer::new_for_http())) .layer(Extension(conn)) .layer(Extension(meili_client)) .layer( // see https://docs.rs/tower-http/latest/tower_http/cors/index.html // for more details // // pay attention that for some request types like posting content-type: application/json // it is required to add ".allow_headers([http::header::CONTENT_TYPE])" // or see this issue https://github.com/tokio-rs/axum/issues/849 CorsLayer::new() .allow_methods([Method::GET, Method::POST]) .allow_origin(cors_url.parse::().unwrap()) .allow_headers(Any), ); let sock_addr = SocketAddr::from(( IpAddr::from_str(opt.addr.as_str()).unwrap_or(IpAddr::V6(Ipv6Addr::LOCALHOST)), opt.port, )); log::info!("listening on http://{}", sock_addr); axum::Server::bind(&sock_addr) .serve(app.into_make_service()) .await .expect("Unable to start server"); } //#[axum_macros::debug_handler] async fn register_handler( Extension(ref conn): Extension, Json(user_sent): Json, ) -> impl IntoResponse { // add to db let user: user::Model = user::Model { id: user_sent.id, user_name: Some(user_sent.clone().name), password_hash: user_sent.clone().password_hash, }; let _created_user = MutationCore::create_user(conn, user) .await .expect("Failed to create user"); return "success"; } async fn list_users(Extension(ref conn): Extension) -> impl IntoResponse { let usersmodels: Vec = QueryCore::list_all_users(&conn).await.unwrap_or(Vec::new()); let mut users: Vec = Vec::new(); for usermodel in usersmodels.iter() { let user = booksman_orm::AxumUser { id: usermodel.id, name: usermodel.user_name.clone().unwrap().clone(), password_hash: "".to_string(), }; users.push(user); } return Json(users); } async fn login_handler( mut auth: AuthContext, Extension(ref conn): Extension, Json(user_sent): Json, ) -> impl IntoResponse { let userdb: user::Model = QueryCore::find_userid_by_name(conn, user_sent.name.clone()) .await .unwrap() .unwrap(); let userid = userdb.id; let corrected_user = booksman_orm::AxumUser { id: userid, name: user_sent.name.clone(), password_hash: user_sent.password_hash.clone(), }; auth.login(&corrected_user).await.unwrap(); return "success"; } // async fn logout_handler(mut auth: AuthContext) { dbg!("Logging out user: {}", &auth.current_user); auth.logout().await; } //https://openlibrary.org/api/books?bibkeys=ISBN:9780980200447&jscmd=data&format=json async fn authentication_check( Extension(_user): Extension, ) -> impl IntoResponse { return Json(true); } //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, Extension(user): Extension, axum::extract::Query(params): axum::extract::Query>, ) -> impl IntoResponse { dotenvy::dotenv().ok(); let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file"); let userid = user.id; 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 bookfound = resjson.book_items.get(isbnstring); if !bookfound.clone().is_none() { 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 temp_cover = coverurl.clone(); let img_id = temp_cover.split("/").last().unwrap(); image .save(format!("{}/{}", images_dir, img_id)) .expect("Failed to save image"); 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() { let authors_name = Some( bookfound .clone() .unwrap() .authors .iter() .map(|s| s.name.clone()) .collect(), ); //} else { // authors_name = None //} let persons: Option>; //= 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 places: Option>; 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 subjects: Option>; 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 times: Option>; 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 mut doc_sent = BookUI { id: 1, open_library_key: Some(bookfound.clone().unwrap().key.clone()), title: bookfound.clone().unwrap().title.clone(), edition_count: None, first_publish_year: None, median_page_count: None, goodread_id: Some(goodread_id), description: None, cover: Some(cover_id.clone()), location: None, time_added: None, rating: None, comments: Some("From openlibrary".to_string()), author_name: authors_name, person: persons, place: places, subject: subjects, time: times, isbn: Some(vec![isbnstring.split("ISBN:").last().unwrap().to_string()]), }; doc_sent.set_descriptions().await; 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_id.to_owned()), rating: doc_sent.rating.to_owned(), time_added: doc_sent.time_added.to_owned(), id: 1, location: doc_sent.location.to_owned(), user_id: userid, }; 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, userid, meili_client).await; } else { println!("ISBN Not found : {}", isbnstring); } } return "Done"; } async fn search_openlibrary( axum::extract::Query(params): axum::extract::Query>, ) -> impl IntoResponse { print!("Get items with query params: {:?}\n", params); let search = params.get("search").unwrap(); let query = format!("https://openlibrary.org/search.json?q={}", search); let res = reqwest::get(query).await.expect("Unable to request"); let mut resjson = res.json::().await.expect("Unable to return value"); resjson.docs.truncate(12); resjson.set_all_cover_urls(); //resjson.set_all_descriptions().await; print!("Search token {:?}\n", search); let mut vec = Vec::with_capacity(12); for i in 0..12 as usize { 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(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: Some(goodread), 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); } async fn delete_book( Extension(ref conn): Extension, Extension(ref meili_client): Extension, Extension(user): Extension, Path(id): Path, ) -> impl IntoResponse { MutationCore::delete_book(conn, id) .await .expect("could not delete book"); let userid = user.id; booksman_search::delete_book(id, userid, meili_client).await; "success" } async fn list_book( Extension(ref conn): Extension, axum::extract::Query(params): axum::extract::Query>, ) -> 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 userid: i32 = params.get("userid").unwrap().parse().unwrap(); let sort: String = params.get("sort").unwrap().to_string(); let per_page: usize = 12; let books = QueryCore::find_books_plus_meta_in_page(conn, page, per_page, userid, sort) .await .expect("could not list books"); let mut resbooks: Vec = Vec::with_capacity(per_page); for bookandmeta in books.0.into_iter() { let mut cover = bookandmeta.clone().book.cover; if !cover.is_none() { cover = Some(format!("{}/images/{}", backend_url, cover.unwrap())); } else { cover = Some(format!("{}/images/placeholder.jpg", backend_url)); } let bookui = BookUI { id: bookandmeta.clone().book.id, title: bookandmeta.clone().book.title, open_library_key: bookandmeta.clone().book.open_library_key, edition_count: bookandmeta.clone().book.edition_count, first_publish_year: bookandmeta.clone().book.first_publish_year, median_page_count: bookandmeta.clone().book.median_page_count, goodread_id: bookandmeta.clone().book.goodread_id, description: bookandmeta.clone().book.description, cover: cover.clone(), location: bookandmeta.clone().book.location, time_added: bookandmeta.clone().book.time_added, rating: bookandmeta.clone().book.rating, comments: bookandmeta.clone().book.comments, author_name: Some( bookandmeta .clone() .authors .into_iter() .map(|u| u.author_name) .collect(), ), person: Some( bookandmeta .clone() .persons .into_iter() .map(|u| u.person) .collect(), ), place: Some( bookandmeta .clone() .places .into_iter() .map(|u| u.place) .collect(), ), subject: Some( bookandmeta .clone() .subjects .into_iter() .map(|u| u.subject) .collect(), ), time: Some( bookandmeta .clone() .times .into_iter() .map(|u| u.time) .collect(), ), isbn: Some( bookandmeta .clone() .isbns .into_iter() .map(|u| u.isbn) .collect(), ), }; resbooks.push(bookui); } let res = PaginatedBookUIList { num_pages: books.1 as u32, books: resbooks, }; return Json(res); // "success" } 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 userid: i32 = params.get("userid").unwrap().parse().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, userid, meili_client) .await .expect("could not list books"); let mut resbooks: Vec = Vec::with_capacity(12); for bookmeili in books.0.into_iter() { let mut cover = bookmeili.clone().cover; if cover != "".to_string() { cover = format!("{}/images/{}", backend_url, cover); } else { cover = format!("{}/images/placeholder.jpg", backend_url); } 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: (books.1 / 12 + 1) as u32, books: resbooks, }; return Json(res); // "success" } async fn create_book( Extension(ref conn): Extension, Extension(ref meili_client): Extension, Extension(user): Extension, Json(doc_sent_orig): Json, ) -> impl IntoResponse { println!("Creating book"); dotenvy::dotenv().ok(); let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file"); let userid = user.id; let mut doc_sent = doc_sent_orig.clone(); let mut cover = doc_sent.cover.clone(); if doc_sent_orig.description.is_none() { doc_sent.set_descriptions().await; } else { if doc_sent_orig.description.unwrap() == "".to_string() { doc_sent.set_descriptions().await; } } if !doc_sent.cover.is_none() { if !doc_sent.cover.clone().unwrap().is_empty() { let img_bytes = reqwest::get(cover.unwrap()) .await .unwrap() .bytes() .await .unwrap(); let image = image::load_from_memory(&img_bytes).unwrap(); let temp_cover = doc_sent.cover.clone().unwrap(); let img_id = format!( "{}{}", Local::now().format("%Y-%m-%d-%H-%M-%S"), temp_cover.split("/").last().unwrap() ); image .save(format!("{}/{}", images_dir, 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(), 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: 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(), user_id: userid, }; 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_or(&vec!["".to_string()]) .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_or(&vec!["".to_string()]) .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_or(&vec!["".to_string()]) .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_or(&vec!["".to_string()]) .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_or(&vec!["".to_string()]) .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_or(&vec!["".to_string()]) .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: 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, userid, meili_client).await; "success" } async fn update_book( Extension(ref conn): Extension, Extension(ref meili_client): Extension, Extension(user): Extension, Json(doc_sent): Json, ) -> impl IntoResponse { println!("Updating book"); dotenvy::dotenv().ok(); let images_dir = env::var("IMAGES_DIR").expect("IMAGES_DIR is not set in .env file"); let backend_url = env::var("BACKEND_URL").expect("BACKEND_URL is not set in .env file"); let userid = user.id.clone(); let mut cover = doc_sent.cover.clone(); if !doc_sent.cover.is_none() { if !doc_sent.cover.clone().unwrap().is_empty() { if doc_sent.cover.clone().unwrap().contains(&backend_url) { cover = Some( doc_sent .cover .clone() .unwrap() .split("/") .last() .unwrap() .to_string(), ); } else { let img_bytes = reqwest::get(cover.unwrap()) .await .unwrap() .bytes() .await .unwrap(); let image = image::load_from_memory(&img_bytes).unwrap(); let temp_cover = doc_sent.cover.clone().unwrap(); let img_id = format!( "{}{}", Local::now().format("%Y-%m-%d-%H-%M-%S"), temp_cover.split("/").last().unwrap() ); image .save(format!("{}/{}", images_dir, 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(), 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: cover.to_owned(), rating: doc_sent.rating.to_owned(), time_added: doc_sent.time_added.to_owned(), id: doc_sent.id.to_owned(), location: doc_sent.location.to_owned(), user_id: userid, }; MutationCore::update_book_by_id(conn, book.id, book) .await .expect("could not update book"); MutationCore::delete_book_author(conn, doc_sent.id) .await .expect("could not delete book authors while updating"); MutationCore::delete_book_person(conn, doc_sent.id) .await .expect("could not delete book persons while updating"); MutationCore::delete_book_place(conn, doc_sent.id) .await .expect("could not delete book places while updating"); MutationCore::delete_book_subject(conn, doc_sent.id) .await .expect("could not delete book subjects while updating"); MutationCore::delete_book_time(conn, doc_sent.id) .await .expect("could not delete book times while updating"); MutationCore::delete_book_isbn(conn, doc_sent.id) .await .expect("could not delete book isbns while updating"); for author_name in doc_sent .author_name .as_ref() .unwrap_or(&vec!["".to_string()]) .iter() { let record: book_author::Model = book_author::Model { id: 1, book_id: doc_sent.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_or(&vec!["".to_string()]) .iter() { let record: book_person::Model = book_person::Model { id: 1, book_id: doc_sent.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_or(&vec!["".to_string()]) .iter() { let record: book_place::Model = book_place::Model { id: 1, book_id: doc_sent.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_or(&vec!["".to_string()]) .iter() { let record: book_subject::Model = book_subject::Model { id: 1, book_id: doc_sent.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_or(&vec!["".to_string()]) .iter() { let record: book_time::Model = book_time::Model { id: 1, book_id: doc_sent.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_or(&vec!["".to_string()]) .iter() { let record: book_isbn::Model = book_isbn::Model { id: 1, book_id: doc_sent.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: 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, userid, meili_client).await; "success" }