use axum::{ http::{HeaderValue, Method, StatusCode}, response::IntoResponse, routing::{get,post,get_service}, Json, Router, extract::{Extension, Path}, }; use axum_extra::routing::SpaRouter; use clap::Parser; use image; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::str::FromStr; use std::io; use tower::ServiceBuilder; use tower_http::services::ServeDir; use tower_http::cors::{Any,CorsLayer}; use tower_http::trace::TraceLayer; use booksman_orm::{ sea_orm::{Database, DatabaseConnection}, Mutation as MutationCore, Query as QueryCore, BookAndMeta, }; use itertools::Itertools; use ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn}; 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, goodread_id: 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)] struct DescriptionOL { value: Option, } 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/{}-L.jpg", cover_i )) } None => (), } } } async fn set_all_descriptions(&mut self) { for book in self.docs.iter_mut() { let query = format!("https://openlibrary.org/{}.json", book.key); let res = reqwest::get(query).await.expect("Unable to request"); let resjson = res.json::().await.expect("Unable to return value"); let description = resjson.description; match description { Some(description) => { match description.value { Some(value) => book.description = Some(value), None => (), } } None => (), } } } } 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 = "::1")] 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, } #[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 db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let conn = Database::connect(db_url) .await .expect("Database connection failed"); //Migrator::up(&conn, None).await.unwrap(); let app = Router::new() .route("/api/search_openlibrary", get(search_openlibrary)) .route("/api/delete/:id", get(delete_book)) .route("/api/list", get(list_book)) .route("/api/create", post(create_book)) .route("/api/update", post(update_book)) .nest("/images", get_service(ServeDir::new("../images")).handle_error(handle_error)) .merge(SpaRouter::new("/assets", opt.static_dir)) .layer(ServiceBuilder::new().layer(TraceLayer::new_for_http())) .layer(Extension(conn)) .layer( // 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("http://localhost:8080".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"); } 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(); vec.push( BookUI{ id: -((i+1) as i32), open_library_key: Some(doc.key), title: doc.title, edition_count: doc.edition_count, first_publish_year: doc.first_publish_year, median_page_count: doc.number_of_pages_median, goodread_id: doc.goodread_id, description: doc.description, cover: doc.cover_url, location: None, time_added: None, rating: None, comments: None, author_name: doc.author_name, person: doc.person, place: doc.place, subject: doc.subject, time: doc.time, isbn: doc.isbn } ); } return Json(vec); } async fn delete_book( Extension(ref conn): Extension, Path(id): Path, ) -> impl IntoResponse { MutationCore::delete_book(conn, id) .await .expect("could not delete book"); "success" } async fn list_book( Extension(ref conn): Extension, axum::extract::Query(params): axum::extract::Query>, ) -> impl IntoResponse { let page: usize = params.get("page").unwrap().parse().unwrap(); let books = QueryCore::find_books_plus_meta_in_page(conn,page,5) .await .expect("could not list books"); let mut resbooks: Vec = Vec::with_capacity(5); for bookandmeta in books.0.into_iter() { let mut cover = bookandmeta.clone().book.cover; if !cover.is_none() { cover = Some(format!("http://localhost:8081/images/{}",cover.unwrap())); } 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 create_book( Extension(ref conn): Extension, Json(doc_sent): Json, ) -> impl IntoResponse { println!("Creating book"); let mut cover = doc_sent.cover.clone(); if !doc_sent.cover.is_none() { let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap(); //.expect("Could not fetch image"); //let img_bytes = img_resp.unwrap().bytes(); let image = image::load_from_memory(&img_bytes).unwrap(); let temp_cover = doc_sent.cover.clone().unwrap(); let img_id = temp_cover.split("/").last().unwrap(); image.save(format!("../images/{}",img_id)).expect("Failed to save image"); cover = Some(img_id.to_string()); } let book: book::Model = book::Model{ 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(), }; 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"); } "success" } async fn update_book( Extension(ref conn): Extension, Json(doc_sent): Json, ) -> impl IntoResponse { println!("Updating book"); let mut cover = doc_sent.cover.clone(); if !doc_sent.cover.is_none() { let img_bytes = reqwest::get(cover.unwrap()).await.unwrap().bytes().await.unwrap(); //.expect("Could not fetch image"); //let img_bytes = img_resp.unwrap().bytes(); let image = image::load_from_memory(&img_bytes).unwrap(); let temp_cover = doc_sent.cover.clone().unwrap(); let img_id = temp_cover.split("/").last().unwrap(); image.save(format!("../images/{}",img_id)).expect("Failed to save image"); cover = Some(img_id.to_string()); } let book: book::Model = book::Model{ open_library_key: doc_sent.open_library_key.to_owned(), title: (doc_sent.title.to_owned()), edition_count: doc_sent.edition_count.to_owned(), 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(), }; let created_book = 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().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().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().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().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().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().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"); } "success" }