Merge branch '21-upgrade-to-leptos-0-6' into 24-switch-from-actix-to-axum
This commit is contained in:
@ -16,7 +16,7 @@ pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
// injects a stylesheet into the document <head>
|
||||
// id=leptos means cargo-leptos will hot-reload this stylesheet
|
||||
<Stylesheet id="leptos" href="/pkg/leptos_start.css"/>
|
||||
<Stylesheet id="leptos" href="/pkg/libretunes.css"/>
|
||||
|
||||
// sets the document title
|
||||
<Title text="LibreTunes"/>
|
||||
@ -44,7 +44,7 @@ pub fn App() -> impl IntoView {
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
fn HomePage() -> impl IntoView {
|
||||
let mut play_status = PlayStatus::default();
|
||||
let play_status = PlayStatus::default();
|
||||
let play_status = create_rw_signal(play_status);
|
||||
|
||||
view! {
|
||||
|
46
src/auth.rs
46
src/auth.rs
@ -1,4 +1,5 @@
|
||||
use leptos::*;
|
||||
|
||||
use crate::models::User;
|
||||
|
||||
/// Create a new user and log them in
|
||||
@ -11,6 +12,7 @@ pub async fn signup(new_user: User) -> Result<(), ServerFnError> {
|
||||
use leptos_actix::extract;
|
||||
use actix_web::{HttpMessage, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use leptos::server_fn::error::NoCustomError;
|
||||
|
||||
// Ensure the user has no id
|
||||
let new_user = User {
|
||||
@ -19,13 +21,17 @@ pub async fn signup(new_user: User) -> Result<(), ServerFnError> {
|
||||
};
|
||||
|
||||
create_user(&new_user).await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("Error creating user: {}", e)))?;
|
||||
.map_err(|e| ServerFnError::<NoCustomError>::ServerError(format!("Error creating user: {}", e)))?;
|
||||
|
||||
extract(|request: HttpRequest| async move {
|
||||
Identity::login(&request.extensions(), new_user.username.clone())
|
||||
}).await??;
|
||||
|
||||
Ok(())
|
||||
match extract::<HttpRequest>().await {
|
||||
Ok(request) => {
|
||||
match Identity::login(&request.extensions(), new_user.username.clone()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(ServerFnError::<NoCustomError>::ServerError(format!("Error logging in user: {}", e))),
|
||||
}
|
||||
},
|
||||
Err(e) => Err(ServerFnError::<NoCustomError>::ServerError(format!("Error getting request: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Log a user in
|
||||
@ -37,20 +43,25 @@ pub async fn login(username_or_email: String, password: String) -> Result<bool,
|
||||
use actix_web::{HttpMessage, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use leptos_actix::extract;
|
||||
use leptos::server_fn::error::NoCustomError;
|
||||
|
||||
let possible_user = validate_user(username_or_email, password).await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("Error validating user: {}", e)))?;
|
||||
.map_err(|e| ServerFnError::<NoCustomError>::ServerError(format!("Error validating user: {}", e)))?;
|
||||
|
||||
let user = match possible_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(false)
|
||||
};
|
||||
|
||||
extract(|request: HttpRequest| async move {
|
||||
Identity::login(&request.extensions(), user.username.clone())
|
||||
}).await??;
|
||||
|
||||
Ok(true)
|
||||
match extract::<HttpRequest>().await {
|
||||
Ok(request) => {
|
||||
match Identity::login(&request.extensions(), user.username.clone()) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => Err(ServerFnError::<NoCustomError>::ServerError(format!("Error logging in user: {}", e))),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(ServerFnError::<NoCustomError>::ServerError(format!("Error getting request: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Log a user out
|
||||
@ -59,12 +70,13 @@ pub async fn login(username_or_email: String, password: String) -> Result<bool,
|
||||
pub async fn logout() -> Result<(), ServerFnError> {
|
||||
use leptos_actix::extract;
|
||||
use actix_identity::Identity;
|
||||
use leptos::server_fn::error::NoCustomError;
|
||||
|
||||
extract(|user: Option<Identity>| async move {
|
||||
if let Some(user) = user {
|
||||
user.logout();
|
||||
}
|
||||
}).await?;
|
||||
match extract::<Option<Identity>>().await {
|
||||
Ok(Some(user)) => user.logout(),
|
||||
Ok(None) => {},
|
||||
Err(e) => return Err(ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {}", e))),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::logging::log;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos::logging::log;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::env;
|
||||
|
@ -28,7 +28,6 @@ if #[cfg(feature = "hydrate")] {
|
||||
#[wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
use app::*;
|
||||
use leptos::*;
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::time::SystemTime;
|
||||
use std::error::Error;
|
||||
use time::Date;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -9,6 +8,7 @@ cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use diesel::prelude::*;
|
||||
use crate::database::PgPooledConn;
|
||||
use std::error::Error;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::auth::login;
|
||||
use leptos::leptos_dom::*;
|
||||
use leptos::*;
|
||||
use leptos_icons::AiIcon::*;
|
||||
use leptos_icons::IoIcon::*;
|
||||
use leptos_icons::*;
|
||||
use icondata;
|
||||
|
||||
#[component]
|
||||
pub fn Login() -> impl IntoView {
|
||||
@ -42,7 +41,7 @@ pub fn Login() -> impl IntoView {
|
||||
view! {
|
||||
<div class="auth-page-container">
|
||||
<div class="login-container">
|
||||
<a class="return" href="/"><Icon icon=Icon::from(IoReturnUpBackSharp) /></a>
|
||||
<a class="return" href="/"><Icon icon=icondata::IoReturnUpBackSharp /></a>
|
||||
<div class="header">
|
||||
<h1>LibreTunes</h1>
|
||||
</div>
|
||||
@ -70,11 +69,11 @@ pub fn Login() -> impl IntoView {
|
||||
<Show
|
||||
when=move || {show_password() == false}
|
||||
fallback=move || view!{ <button on:click=toggle_password class="login-password-visibility">
|
||||
<Icon icon=Icon::from(AiEyeInvisibleFilled) />
|
||||
<Icon icon=icondata::AiEyeInvisibleFilled />
|
||||
</button> /> }
|
||||
>
|
||||
<button on:click=toggle_password class="login-password-visibility">
|
||||
<Icon icon=Icon::from(AiEyeFilled) />
|
||||
<Icon icon=icondata::AiEyeFilled />
|
||||
</button>
|
||||
|
||||
</Show>
|
||||
|
@ -1,11 +1,9 @@
|
||||
use crate::auth::signup;
|
||||
use crate::models::User;
|
||||
use leptos::ev::input;
|
||||
use leptos::leptos_dom::*;
|
||||
use leptos::*;
|
||||
use leptos_icons::AiIcon::*;
|
||||
use leptos_icons::IoIcon::*;
|
||||
use leptos_icons::*;
|
||||
use icondata;
|
||||
|
||||
#[component]
|
||||
pub fn Signup() -> impl IntoView {
|
||||
@ -15,8 +13,6 @@ pub fn Signup() -> impl IntoView {
|
||||
|
||||
let (show_password, set_show_password) = create_signal(false);
|
||||
|
||||
let navigate = leptos_router::use_navigate();
|
||||
|
||||
let toggle_password = move |_| {
|
||||
set_show_password.update(|show_password| *show_password = !*show_password);
|
||||
log!("showing password");
|
||||
@ -49,7 +45,7 @@ pub fn Signup() -> impl IntoView {
|
||||
view! {
|
||||
<div class="auth-page-container">
|
||||
<div class="signup-container">
|
||||
<a class="return" href="/"><Icon icon=Icon::from(IoReturnUpBackSharp) /></a>
|
||||
<a class="return" href="/"><Icon icon=icondata::IoReturnUpBackSharp /></a>
|
||||
<div class="header">
|
||||
<h1>LibreTunes</h1>
|
||||
</div>
|
||||
@ -86,10 +82,10 @@ pub fn Signup() -> impl IntoView {
|
||||
<i></i>
|
||||
<Show
|
||||
when=move || {show_password() == false}
|
||||
fallback=move || view!{ <button on:click=toggle_password class="password-visibility"> <Icon icon=Icon::from(AiEyeInvisibleFilled) /></button> /> }
|
||||
fallback=move || view!{ <button on:click=toggle_password class="password-visibility"> <Icon icon=icondata::AiEyeInvisibleFilled /></button> /> }
|
||||
>
|
||||
<button on:click=toggle_password class="password-visibility">
|
||||
<Icon icon=Icon::from(AiEyeFilled) />
|
||||
<Icon icon=icondata::AiEyeFilled />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
@ -1,12 +1,8 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::playstatus::PlayStatus;
|
||||
use leptos::ev::MouseEvent;
|
||||
use leptos::html::{Audio, Div};
|
||||
use leptos::leptos_dom::*;
|
||||
use leptos::*;
|
||||
use leptos_icons::BsIcon::*;
|
||||
use leptos_icons::RiIcon::*;
|
||||
use leptos_icons::*;
|
||||
|
||||
/// Width and height of the forward/backward skip buttons
|
||||
@ -195,9 +191,9 @@ fn PlayControls(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
let icon = Signal::derive(move || {
|
||||
status.with(|status| {
|
||||
if status.playing {
|
||||
Icon::from(BsPauseFill)
|
||||
icondata::BsPauseFill
|
||||
} else {
|
||||
Icon::from(BsPlayFill)
|
||||
icondata::BsPlayFill
|
||||
}
|
||||
})
|
||||
});
|
||||
@ -206,7 +202,7 @@ fn PlayControls(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
<div class="playcontrols" align="center">
|
||||
|
||||
<button on:click=skip_back on:mousedown=prevent_focus>
|
||||
<Icon class="controlbtn" width=SKIP_BTN_SIZE height=SKIP_BTN_SIZE icon=Icon::from(BsSkipStartFill) />
|
||||
<Icon class="controlbtn" width=SKIP_BTN_SIZE height=SKIP_BTN_SIZE icon=icondata::BsSkipStartFill />
|
||||
</button>
|
||||
|
||||
<button on:click=toggle_play on:mousedown=prevent_focus>
|
||||
@ -214,7 +210,7 @@ fn PlayControls(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
</button>
|
||||
|
||||
<button on:click=skip_forward on:mousedown=prevent_focus>
|
||||
<Icon class="controlbtn" width=SKIP_BTN_SIZE height=SKIP_BTN_SIZE icon=Icon::from(BsSkipEndFill) />
|
||||
<Icon class="controlbtn" width=SKIP_BTN_SIZE height=SKIP_BTN_SIZE icon=icondata::BsSkipEndFill />
|
||||
</button>
|
||||
|
||||
</div>
|
||||
@ -339,7 +335,7 @@ fn QueueToggle(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
view! {
|
||||
<div class="queue-toggle">
|
||||
<button on:click=update_queue on:mousedown=prevent_focus>
|
||||
<Icon class="controlbtn" width=QUEUE_BTN_SIZE height=QUEUE_BTN_SIZE icon=Icon::from(RiPlayListMediaFill) />
|
||||
<Icon class="controlbtn" width=QUEUE_BTN_SIZE height=QUEUE_BTN_SIZE icon=icondata::RiPlayListMediaFill />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ use leptos::ev::MouseEvent;
|
||||
use leptos::leptos_dom::*;
|
||||
use leptos::*;
|
||||
use leptos_icons::*;
|
||||
use leptos_icons::CgIcon::*;
|
||||
use leptos::ev::DragEvent;
|
||||
|
||||
const RM_BTN_SIZE: &str = "2.5rem";
|
||||
@ -106,7 +105,7 @@ pub fn Queue(status: RwSignal<PlayStatus>) -> impl IntoView {
|
||||
<p>Playing</p>
|
||||
}>
|
||||
<button on:click=move |_| remove_song(index) on:mousedown=prevent_focus>
|
||||
<Icon class="remove-song" width=RM_BTN_SIZE height=RM_BTN_SIZE icon=Icon::from(CgTrash) />
|
||||
<Icon class="remove-song" width=RM_BTN_SIZE height=RM_BTN_SIZE icon=icondata::CgTrash />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
20
src/users.rs
20
src/users.rs
@ -21,12 +21,13 @@ use crate::models::User;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn find_user(username_or_email: String) -> Result<Option<User>, ServerFnError> {
|
||||
use crate::schema::users::dsl::*;
|
||||
use leptos::server_fn::error::NoCustomError;
|
||||
|
||||
// Look for either a username or email that matches the input, and return an option with None if no user is found
|
||||
let db_con = &mut get_db_conn();
|
||||
let user = users.filter(username.eq(username_or_email.clone())).or_filter(email.eq(username_or_email))
|
||||
.first::<User>(db_con).optional()
|
||||
.map_err(|e| ServerFnError::ServerError(format!("Error getting user from database: {}", e)))?;
|
||||
.map_err(|e| ServerFnError::<NoCustomError>::ServerError(format!("Error getting user from database: {}", e)))?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
@ -36,13 +37,14 @@ pub async fn find_user(username_or_email: String) -> Result<Option<User>, Server
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn create_user(new_user: &User) -> Result<(), ServerFnError> {
|
||||
use crate::schema::users::dsl::*;
|
||||
use leptos::server_fn::error::NoCustomError;
|
||||
|
||||
let new_password = new_user.password.clone()
|
||||
.ok_or(ServerFnError::ServerError(format!("No password provided for user {}", new_user.username)))?;
|
||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(format!("No password provided for user {}", new_user.username)))?;
|
||||
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let password_hash = Pbkdf2.hash_password(new_password.as_bytes(), &salt)
|
||||
.map_err(|_| ServerFnError::ServerError("Error hashing password".to_string()))?.to_string();
|
||||
.map_err(|_| ServerFnError::<NoCustomError>::ServerError("Error hashing password".to_string()))?.to_string();
|
||||
|
||||
let new_user = User {
|
||||
password: Some(password_hash),
|
||||
@ -52,7 +54,7 @@ pub async fn create_user(new_user: &User) -> Result<(), ServerFnError> {
|
||||
let db_con = &mut get_db_conn();
|
||||
|
||||
diesel::insert_into(users).values(&new_user).execute(db_con)
|
||||
.map_err(|e| ServerFnError::ServerError(format!("Error creating user: {}", e)))?;
|
||||
.map_err(|e| ServerFnError::<NoCustomError>::ServerError(format!("Error creating user: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -61,8 +63,10 @@ pub async fn create_user(new_user: &User) -> Result<(), ServerFnError> {
|
||||
/// Returns a Result with the user if the credentials are valid, None if not valid, or an error if there was a problem
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn validate_user(username_or_email: String, password: String) -> Result<Option<User>, ServerFnError> {
|
||||
use leptos::server_fn::error::NoCustomError;
|
||||
|
||||
let db_user = find_user(username_or_email.clone()).await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("Error getting user from database: {}", e)))?;
|
||||
.map_err(|e| ServerFnError::<NoCustomError>::ServerError(format!("Error getting user from database: {}", e)))?;
|
||||
|
||||
// If the user is not found, return None
|
||||
let db_user = match db_user {
|
||||
@ -71,10 +75,10 @@ pub async fn validate_user(username_or_email: String, password: String) -> Resul
|
||||
};
|
||||
|
||||
let db_password = db_user.password.clone()
|
||||
.ok_or(ServerFnError::ServerError(format!("No password found for user {}", db_user.username)))?;
|
||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(format!("No password found for user {}", db_user.username)))?;
|
||||
|
||||
let password_hash = PasswordHash::new(&db_password)
|
||||
.map_err(|e| ServerFnError::ServerError(format!("Error hashing supplied password: {}", e)))?;
|
||||
.map_err(|e| ServerFnError::<NoCustomError>::ServerError(format!("Error hashing supplied password: {}", e)))?;
|
||||
|
||||
match Pbkdf2.verify_password(password.as_bytes(), &password_hash) {
|
||||
Ok(()) => {},
|
||||
@ -82,7 +86,7 @@ pub async fn validate_user(username_or_email: String, password: String) -> Resul
|
||||
return Ok(None);
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(ServerFnError::ServerError(format!("Error verifying password: {}", e)));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(format!("Error verifying password: {}", e)));
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user