Web API, Web UI #86

Merged
projectmoon merged 37 commits from web-api into master 2021-07-15 15:04:54 +00:00
8 changed files with 160 additions and 24 deletions
Showing only changes of commit d082d10c7a - Show all commits

1
Cargo.lock generated
View File

@ -3578,6 +3578,7 @@ dependencies = [
"graphql_client_web", "graphql_client_web",
"js-sys", "js-sys",
"serde", "serde",
"serde_json",
"tenebrous-api", "tenebrous-api",
"thiserror", "thiserror",
"wasm-bindgen", "wasm-bindgen",

View File

@ -30,6 +30,7 @@ js-sys = "0.3"
graphql_client = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" } graphql_client = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" }
graphql_client_web = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" } graphql_client_web = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" }
serde = { version = "1.0.67", features = ["derive"] } serde = { version = "1.0.67", features = ["derive"] }
serde_json = {version = "1.0" }
thiserror = "1.0" thiserror = "1.0"
[dependencies.web-sys] [dependencies.web-sys]

View File

@ -17,21 +17,25 @@ async fn make_request(request: Request) -> Result<JsValue, UiError> {
let resp: Response = resp_value.dyn_into().unwrap(); let resp: Response = resp_value.dyn_into().unwrap();
let json = JsFuture::from(resp.json()?).await?; let json = JsFuture::from(resp.json()?).await?;
console::log_1(&json);
Ok(json) Ok(json)
} }
pub async fn fetch_jwt() -> Result<String, UiError> { pub async fn login(username: &str, password: &str) -> Result<String, UiError> {
let mut opts = RequestInit::new(); let mut opts = RequestInit::new();
opts.method("POST"); opts.method("POST");
opts.mode(RequestMode::Cors); opts.mode(RequestMode::Cors);
opts.credentials(RequestCredentials::Include); opts.credentials(RequestCredentials::Include);
opts.body(Some(&JsValue::from_str( let body = JsValue::from_str(
r#" &serde_json::json!({
{ "username": "@projectmoon:agnos.is", "password": "lolol" } "username": username,
"#, "password": password
))); })
.to_string(),
);
opts.body(Some(&body));
let url = format!("http://localhost:10000/login"); let url = format!("http://localhost:10000/login");
let request = Request::new_with_str_and_init(&url, &opts)?; let request = Request::new_with_str_and_init(&url, &opts)?;
@ -59,7 +63,6 @@ pub async fn refresh_jwt() -> Result<String, UiError> {
//TODO don't unwrap the response. OR... change it so we have a standard response. //TODO don't unwrap the response. OR... change it so we have a standard response.
let response = make_request(request).await?; let response = make_request(request).await?;
console::log_1(&response);
let response: LoginResponse = response.into_serde().unwrap(); let response: LoginResponse = response.into_serde().unwrap();
Ok(response.jwt_token) Ok(response.jwt_token)

View File

@ -0,0 +1,137 @@
use crate::error::UiError;
use crate::state::{Action, Room, WebUiDispatcher};
use crate::{api, state::WebUiState};
use std::sync::Arc;
use wasm_bindgen_futures::spawn_local;
use web_sys::console;
use web_sys::FocusEvent;
use yew::prelude::*;
use yewdux::dispatch::Dispatcher;
use yewdux::prelude::*;
use yewtil::NeqAssign;
#[doc(hidden)]
pub(crate) struct YewduxLogin {
dispatch: Arc<WebUiDispatcher>,
link: ComponentLink<YewduxLogin>,
username: String,
password: String,
}
pub enum LoginAction {
UpdateUsername(String),
UpdatePassword(String),
Login,
Noop,
}
pub(crate) type Login = WithDispatch<YewduxLogin>;
async fn do_login(
dispatch: &WebUiDispatcher,
username: &str,
password: &str,
) -> Result<(), UiError> {
let jwt = api::auth::login(username, password).await?;
//state.jwt_token = Some(jwt);
dispatch.send(Action::UpdateJwt(jwt));
Ok(())
}
async fn do_refresh_jwt(dispatch: &WebUiDispatcher) -> Result<(), UiError> {
let jwt = api::auth::refresh_jwt().await?;
dispatch.send(Action::UpdateJwt(jwt));
Ok(())
}
impl Component for YewduxLogin {
type Message = LoginAction;
type Properties = WebUiDispatcher;
fn create(dispatch: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
dispatch: Arc::new(dispatch),
link,
username: "".to_string(),
password: "".to_string(),
}
}
fn update(&mut self, action: Self::Message) -> ShouldRender {
match action {
LoginAction::UpdateUsername(username) => self.username = username,
LoginAction::UpdatePassword(password) => self.password = password,
LoginAction::Login => {
let dispatch = self.dispatch.clone();
let username = self.username.clone();
let password = self.password.clone();
spawn_local(async move {
do_login(&*dispatch, &username, &password).await;
});
}
_ => (),
};
false
}
fn change(&mut self, dispatch: Self::Properties) -> ShouldRender {
self.dispatch.neq_assign(Arc::new(dispatch))
}
fn rendered(&mut self, first_render: bool) {
if first_render {
//
}
}
fn view(&self) -> Html {
let dispatch = Arc::new(self.dispatch.clone());
let dispatch2 = dispatch.clone();
// let do_the_login = self.dispatch.reduce_callback(|state| {
// spawn_local(async move {
// do_login(state, "user", "pw").await;
// });
// });
let do_the_login = self.link.callback(move |e: FocusEvent| {
e.prevent_default();
LoginAction::Login
});
// let refresh_jwt = self.link.callback(move |_| {
// let dispatch = dispatch2.clone();
// spawn_local(async move {
// do_refresh_jwt(&*dispatch).await;
// });
// });
let update_username = self
.link
.callback(|e: InputData| LoginAction::UpdateUsername(e.value));
let update_password = self
.link
.callback(|e: InputData| LoginAction::UpdatePassword(e.value));
html! {
<div>
<form onsubmit=do_the_login>
<label for="username">{"Username:"}</label>
<input oninput=update_username id="username" name="username" type="text" placeholder="Username" />
<label for="password">{"Password:"}</label>
<input oninput=update_password id="password" name="password" type="password" placeholder="Password" />
<input type="submit" value="Log In" />
</form>
//<button onclick=refresh_jwt>{ "Refresh JWT" }</button>
<div>
{ "Current JWT: " }
{ self.dispatch.state().jwt_token.as_ref().unwrap_or(&"[not set]".to_string()) }
</div>
</div>
}
}
fn destroy(&mut self) {}
}

View File

@ -0,0 +1 @@
pub mod login;

View File

@ -15,6 +15,9 @@ pub enum UiError {
#[error("error: {0}")] #[error("error: {0}")]
JsError(String), JsError(String),
#[error("(de)serialization error: {0}")]
SerdeError(#[from] serde_json::Error),
} }
impl From<wasm_bindgen::JsValue> for UiError { impl From<wasm_bindgen::JsValue> for UiError {

View File

@ -1,3 +1,4 @@
use crate::components::login::Login;
use rooms::RoomList; use rooms::RoomList;
use rooms::YewduxRoomList; use rooms::YewduxRoomList;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -6,6 +7,7 @@ use yew_router::prelude::*;
use yewdux::prelude::*; use yewdux::prelude::*;
pub mod api; pub mod api;
pub mod components;
pub mod error; pub mod error;
pub mod grpc; pub mod grpc;
pub mod rooms; pub mod rooms;
@ -38,7 +40,10 @@ fn render_route(routes: &AppRoute) -> Html {
} }
AppRoute::Index => { AppRoute::Index => {
html! { html! {
<div>
<Login />
<RoomList /> <RoomList />
</div>
} }
} }
} }

View File

@ -44,12 +44,6 @@ async fn load_rooms(dispatch: &WebUiDispatcher) -> Result<(), UiError> {
Ok(()) Ok(())
} }
async fn do_jwt_stuff(dispatch: &WebUiDispatcher) -> Result<(), UiError> {
let jwt = api::auth::fetch_jwt().await?;
dispatch.send(Action::UpdateJwt(jwt));
Ok(())
}
async fn do_refresh_jwt(dispatch: &WebUiDispatcher) -> Result<(), UiError> { async fn do_refresh_jwt(dispatch: &WebUiDispatcher) -> Result<(), UiError> {
let jwt = api::auth::refresh_jwt().await?; let jwt = api::auth::refresh_jwt().await?;
dispatch.send(Action::UpdateJwt(jwt)); dispatch.send(Action::UpdateJwt(jwt));
@ -105,13 +99,6 @@ impl Component for YewduxRoomList {
}); });
}); });
let jwt_update = self.link.callback(move |_| {
let dispatch = dispatch2.clone();
spawn_local(async move {
do_jwt_stuff(&*dispatch).await;
});
});
let refresh_jwt = self.link.callback(move |_| { let refresh_jwt = self.link.callback(move |_| {
let dispatch = dispatch3.clone(); let dispatch = dispatch3.clone();
spawn_local(async move { spawn_local(async move {
@ -121,10 +108,8 @@ impl Component for YewduxRoomList {
html! { html! {
<div> <div>
<button onclick=the_future>{ "Add Room" }</button> <button onclick=the_future>{ "Fetch Rooms" }</button>
<button onclick=jwt_update>{ "Fetch JWT" }</button>
<button onclick=refresh_jwt>{ "Refresh JWT" }</button> <button onclick=refresh_jwt>{ "Refresh JWT" }</button>
<div> { "Current JWT: " } { self.dispatch.state().jwt_token.as_ref().unwrap_or(&"[not set]".to_string()) }</div>
<ul> <ul>
{ {
for self.dispatch.state().rooms.iter().map(|room| { for self.dispatch.state().rooms.iter().map(|room| {