User Authentication and Other fixes #1
@@ -12,6 +12,7 @@ use wasm_bindgen::JsCast;
|
|||||||
use web_sys::{Event, HtmlInputElement, KeyboardEvent}; // 0.3.5
|
use web_sys::{Event, HtmlInputElement, KeyboardEvent}; // 0.3.5
|
||||||
use dotenv_codegen::dotenv;
|
use dotenv_codegen::dotenv;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use std::collections::HashMap;
|
||||||
//use gloo_timers::future::TimeoutFuture;
|
//use gloo_timers::future::TimeoutFuture;
|
||||||
//#[macro_use]
|
//#[macro_use]
|
||||||
//extern crate dotenv_codegen;
|
//extern crate dotenv_codegen;
|
||||||
@@ -50,12 +51,20 @@ struct PaginatedBookUIList {
|
|||||||
books: Vec<BookUI>,
|
books: Vec<BookUI>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AxumUser {
|
||||||
|
pub id: i32,
|
||||||
|
pub password_hash: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct AppState {
|
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 internalsearch: RcSignal<bool>,
|
||||||
|
pub pagedisplay: RcSignal<u32>,
|
||||||
pub pagenum: RcSignal<u32>,
|
pub pagenum: RcSignal<u32>,
|
||||||
pub maxpage: RcSignal<u32>,
|
pub maxpage: RcSignal<u32>,
|
||||||
pub adding: RcSignal<bool>,
|
pub adding: RcSignal<bool>,
|
||||||
@@ -68,7 +77,12 @@ pub struct AppState {
|
|||||||
pub updatingrequest: RcSignal<bool>,
|
pub updatingrequest: RcSignal<bool>,
|
||||||
pub addingrequest: RcSignal<bool>,
|
pub addingrequest: RcSignal<bool>,
|
||||||
pub deleteid: RcSignal<i32>,
|
pub deleteid: RcSignal<i32>,
|
||||||
pub deleterequest: RcSignal<bool>
|
pub deleterequest: RcSignal<bool>,
|
||||||
|
pub userlist: RcSignal<HashMap<String, i32>>,
|
||||||
|
pub userid: RcSignal<i32>,
|
||||||
|
pub userscreen: RcSignal<bool>,
|
||||||
|
pub loggedin: RcSignal<bool>,
|
||||||
|
pub editmode: RcSignal<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Route)]
|
#[derive(Route)]
|
||||||
@@ -81,6 +95,54 @@ enum AppRoutes {
|
|||||||
NotFound,
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
let callback: Closure<dyn FnMut()> = Closure::new(move || {
|
||||||
|
info!("Got scroll event");
|
||||||
|
});
|
||||||
|
|
||||||
|
let window = web_sys::window().expect("Failed to get window");
|
||||||
|
window
|
||||||
|
.add_event_listener_with_callback_and_bool(
|
||||||
|
"scroll",
|
||||||
|
callback.as_ref().unchecked_ref(),
|
||||||
|
false, // Changing this to true does not help :/
|
||||||
|
)
|
||||||
|
.expect("Failed to set listener");
|
||||||
|
callback.forget();*/
|
||||||
|
|
||||||
|
async fn login_user(user: AxumUser) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
||||||
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
|
let url = format!("{}/api/login", backend_url);
|
||||||
|
let resp = Request::post(&url).body(serde_wasm_bindgen::to_value(&serde_json::to_string(&user).unwrap()).unwrap()).header("content-type","application/json").send().await?;
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn register_user(user: AxumUser) -> Result<reqwasm::http::Response, reqwasm::Error> {
|
||||||
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
|
let url = format!("{}/api/register", backend_url);
|
||||||
|
let resp = Request::post(&url).body(serde_wasm_bindgen::to_value(&serde_json::to_string(&user).unwrap()).unwrap()).header("content-type","application/json").send().await?;
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn logout_user() -> Result<reqwasm::http::Response, reqwasm::Error> {
|
||||||
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
|
let url = format!("{}/api/logout", backend_url);
|
||||||
|
let resp = Request::post(&url).send().await?;
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_users() -> Result<HashMap<String,i32>, reqwasm::Error> {
|
||||||
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
|
let url = format!("{}/api/list_users", backend_url);
|
||||||
|
let resp = Request::post(&url).send().await?;
|
||||||
|
let users = resp.json::<Vec<AxumUser>>().await?;
|
||||||
|
let mut res = HashMap::new();
|
||||||
|
for user in users {
|
||||||
|
res.insert(user.name, user.id);
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_books(search: String) -> Result<Vec<BookUI>, reqwasm::Error> {
|
async fn fetch_books(search: String) -> Result<Vec<BookUI>, reqwasm::Error> {
|
||||||
//dotenvy::dotenv().ok();
|
//dotenvy::dotenv().ok();
|
||||||
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
let backend_url : &'static str = dotenv!("BACKEND_URL");
|
||||||
@@ -161,6 +223,7 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
|
|||||||
let value3 = create_signal(cx, String::new());
|
let value3 = create_signal(cx, String::new());
|
||||||
let input_ref3 = create_node_ref(cx);
|
let input_ref3 = create_node_ref(cx);
|
||||||
|
|
||||||
|
let editchecked = create_signal(cx, false);
|
||||||
|
|
||||||
let handle_submit = |event: Event| {
|
let handle_submit = |event: Event| {
|
||||||
let event: KeyboardEvent = event.unchecked_into();
|
let event: KeyboardEvent = event.unchecked_into();
|
||||||
@@ -211,32 +274,6 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
if !task.is_empty() {
|
|
||||||
app_state.search.set(task);
|
|
||||||
app_state.openlibrary.set(false);
|
|
||||||
app_state.internalsearch.set(true);
|
|
||||||
app_state.refreshing.set(true);
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let click_addbook = |_| {
|
let click_addbook = |_| {
|
||||||
app_state.adding.set(true);
|
app_state.adding.set(true);
|
||||||
app_state.updating.set(false);
|
app_state.updating.set(false);
|
||||||
@@ -273,28 +310,66 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let click_logout = |_| {
|
||||||
|
spawn_local(async move {
|
||||||
|
logout_user().await.unwrap();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let click_userscreen = |_| {
|
||||||
|
app_state.userscreen.set(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
let toggle_editmode = |_| {
|
||||||
|
app_state.editmode.set(*editchecked.get());
|
||||||
|
};
|
||||||
|
|
||||||
view! { cx,
|
view! { cx,
|
||||||
header(class="header") {
|
header(class="header") {
|
||||||
|
div(class="header-button"){
|
||||||
|
(if *app_state.loggedin.get() == false {
|
||||||
|
view!{ cx,
|
||||||
|
button(on:click=click_userscreen) { "Register/Login" }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view!{cx,
|
||||||
|
button(on:click=click_logout) { "Log Out" }
|
||||||
|
input(
|
||||||
|
class="toggle",
|
||||||
|
type="checkbox",
|
||||||
|
on:input=toggle_editmode,
|
||||||
|
bind:checked=editchecked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(if *app_state.loggedin.get() == false {
|
||||||
|
view!{ cx,
|
||||||
div(class="header-column"){
|
div(class="header-column"){
|
||||||
input(ref=input_ref,
|
input(ref=input_ref,
|
||||||
class="new-todo",
|
class="new-todo",
|
||||||
placeholder="Search openlibrary",
|
placeholder="Search internet (openlibrary)",
|
||||||
bind:value=value,
|
bind:value=value,
|
||||||
on:keyup=handle_submit,
|
on:keyup=handle_submit,
|
||||||
)
|
)
|
||||||
button(on:click=click_searchOL) { "Search OpenLibrary" }
|
} }
|
||||||
}
|
} else {
|
||||||
|
view!{cx, }
|
||||||
|
})
|
||||||
|
|
||||||
div(class="header-column"){
|
div(class="header-column"){
|
||||||
button(on:click=click_listall) { "List All DB" }
|
button(on:click=click_listall) { "All books" }
|
||||||
input(ref=input_ref2,
|
input(ref=input_ref2,
|
||||||
class="new-todo",
|
class="new-todo",
|
||||||
placeholder="Search internal DB",
|
placeholder="Search your library",
|
||||||
bind:value=value2,
|
bind:value=value2,
|
||||||
on:keyup=handle_submit_seachall,
|
on:keyup=handle_submit_seachall,
|
||||||
)
|
)
|
||||||
button(on:click=click_searchall) { "Search internal" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(if *app_state.editmode.get() == false {
|
||||||
|
view!{ cx,
|
||||||
div(class="header-column"){
|
div(class="header-column"){
|
||||||
|
|
||||||
button(on:click=click_addbook) { "+ Add New" }
|
button(on:click=click_addbook) { "+ Add New" }
|
||||||
@@ -306,6 +381,10 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
|
|||||||
)
|
)
|
||||||
button(on:click=click_addbulk) { "Add bulk ISBNs" }
|
button(on:click=click_addbulk) { "Add bulk ISBNs" }
|
||||||
}
|
}
|
||||||
|
} } else {
|
||||||
|
view!{cx, ""}
|
||||||
|
})
|
||||||
|
|
||||||
div(class="header-page-column"){
|
div(class="header-page-column"){
|
||||||
PageBar{}
|
PageBar{}
|
||||||
}
|
}
|
||||||
@@ -995,6 +1074,61 @@ async fn PageBar<G: Html>(cx: Scope<'_>) -> View<G> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
async fn LoginScreenUI<G: Html>(cx: Scope<'_>) -> View<G> {
|
||||||
|
let app_state = use_context::<AppState>(cx);
|
||||||
|
let userval = create_signal(cx, String::new());
|
||||||
|
let input_refU = create_node_ref(cx);
|
||||||
|
let passval = create_signal(cx, String::new());
|
||||||
|
let input_refP = create_node_ref(cx);
|
||||||
|
|
||||||
|
let handle_register = move |_| {
|
||||||
|
let user = AxumUser{
|
||||||
|
id: 0,
|
||||||
|
name: (*userval.get()).clone(),
|
||||||
|
password_hash: (*passval.get()).clone(),
|
||||||
|
};
|
||||||
|
spawn_local(async move {
|
||||||
|
register_user(user)
|
||||||
|
.await.expect("Couldn't register user");
|
||||||
|
});
|
||||||
|
app_state.userscreen.set(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle_login = move |_| {
|
||||||
|
let user = AxumUser{
|
||||||
|
id: 0,
|
||||||
|
name: (*userval.get()).clone(),
|
||||||
|
password_hash: (*passval.get()).clone(),
|
||||||
|
};
|
||||||
|
spawn_local(async move {
|
||||||
|
login_user(user)
|
||||||
|
.await.expect("Couldn't login user");
|
||||||
|
});
|
||||||
|
app_state.userscreen.set(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {cx,
|
||||||
|
div {
|
||||||
|
(if *app_state.userscreen.get() == true {
|
||||||
|
view!{cx,
|
||||||
|
|
||||||
|
input(ref=input_refU,bind:value=userval)
|
||||||
|
input(ref=input_refP,bind:value=passval)
|
||||||
|
|
||||||
|
button(on:click=handle_login){ "LOGIN" }
|
||||||
|
button(on:click=handle_register){ "REGISTER" }
|
||||||
|
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
view!{cx,""}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[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 {
|
||||||
@@ -1002,6 +1136,7 @@ fn App<G: Html>(cx: Scope) -> View<G> {
|
|||||||
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()),
|
internalsearch: create_rc_signal(bool::default()),
|
||||||
|
pagedisplay: create_rc_signal(1),
|
||||||
pagenum: create_rc_signal(1),
|
pagenum: create_rc_signal(1),
|
||||||
maxpage: create_rc_signal(1),
|
maxpage: create_rc_signal(1),
|
||||||
adding: create_rc_signal(bool::default()),
|
adding: create_rc_signal(bool::default()),
|
||||||
@@ -1015,6 +1150,11 @@ fn App<G: Html>(cx: Scope) -> View<G> {
|
|||||||
addingrequest: create_rc_signal(bool::default()),
|
addingrequest: create_rc_signal(bool::default()),
|
||||||
deleteid: create_rc_signal(-1),
|
deleteid: create_rc_signal(-1),
|
||||||
deleterequest: create_rc_signal(bool::default()),
|
deleterequest: create_rc_signal(bool::default()),
|
||||||
|
userlist: create_rc_signal(HashMap::new()),
|
||||||
|
userid: create_rc_signal(0),
|
||||||
|
userscreen: create_rc_signal(bool::default()),
|
||||||
|
loggedin: create_rc_signal(bool::default()),
|
||||||
|
editmode: create_rc_signal(bool::default()),
|
||||||
};
|
};
|
||||||
provide_context(cx, app_state);
|
provide_context(cx, app_state);
|
||||||
view! {
|
view! {
|
||||||
@@ -1022,6 +1162,7 @@ fn App<G: Html>(cx: Scope) -> View<G> {
|
|||||||
div {
|
div {
|
||||||
Header {}
|
Header {}
|
||||||
div(class="main"){
|
div(class="main"){
|
||||||
|
LoginScreenUI{}
|
||||||
AddingUI{}
|
AddingUI{}
|
||||||
SelectedUI{}
|
SelectedUI{}
|
||||||
ListOL {}
|
ListOL {}
|
||||||
|
|||||||
Reference in New Issue
Block a user