diff --git a/Cargo.lock b/Cargo.lock index 5b2514e..1bc5c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-recursion" version = "1.1.0" @@ -153,15 +159,11 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", "sync_wrapper 1.0.0", "tokio", "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -182,7 +184,6 @@ dependencies = [ "sync_wrapper 0.1.2", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -259,6 +260,12 @@ version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +[[package]] +name = "bytemuck" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" + [[package]] name = "byteorder" version = "1.5.0" @@ -1576,7 +1583,6 @@ dependencies = [ "diesel_migrations", "dotenv", "flexi_logger", - "futures", "http", "icondata", "lazy_static", @@ -1586,17 +1592,20 @@ dependencies = [ "leptos_meta", "leptos_router", "log", + "multer", "openssl", "pbkdf2", "serde", + "server_fn", + "symphonia", "thiserror", "time", "tokio", "tower", "tower-http", - "tower-sessions", "tower-sessions-redis-store", "wasm-bindgen", + "web-sys", ] [[package]] @@ -2321,16 +2330,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - [[package]] name = "serde_qs" version = "0.12.0" @@ -2360,23 +2359,11 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "server_fn" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a46a2ffdecb81430ecfb995989218a18b6e94c1ead50cb806b5927c986a8ce" +checksum = "536a5b959673643ee01e59ae41bf01425482c8070dee95d7061ee2d45296b59c" dependencies = [ "axum", "bytes", @@ -2390,6 +2377,7 @@ dependencies = [ "hyper", "inventory", "js-sys", + "multer", "once_cell", "send_wrapper", "serde", @@ -2495,6 +2483,55 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-bundle-mp3", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 52ad1ad..953da57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,43 +8,50 @@ build = "src/build.rs" crate-type = ["cdylib", "rlib"] [dependencies] -console_error_panic_hook = "0.1" +console_error_panic_hook = { version = "0.1", optional = true } cfg-if = "1" -http = "1.0" -leptos = { version = "0.6", features = ["nightly"] } +http = { version = "1.0", default-features = false } +leptos = { version = "0.6", default-features = false, features = ["nightly"] } leptos_meta = { version = "0.6", features = ["nightly"] } leptos_axum = { version = "0.6", optional = true } leptos_router = { version = "0.6", features = ["nightly"] } -wasm-bindgen = "=0.2.92" +wasm-bindgen = { version = "=0.2.92", default-features = false, optional = true } leptos_icons = { version = "0.3.0" } icondata = { version = "0.3.0" } dotenv = { version = "0.15.0", optional = true } -diesel = { version = "2.1.4", features = ["postgres", "r2d2", "time"], optional = true } +diesel = { version = "2.1.4", features = ["postgres", "r2d2", "time"], default-features = false, optional = true } lazy_static = { version = "1.4.0", optional = true } -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"], default-features = false } openssl = { version = "0.10.63", optional = true } -time = { version = "0.3.34", features = ["serde"] } +time = { version = "0.3.34", features = ["serde"], default-features = false } diesel_migrations = { version = "2.1.0", optional = true } pbkdf2 = { version = "0.12.2", features = ["simple"], optional = true } -futures = { version = "0.3.30", default-features = false, optional = true } tokio = { version = "1", optional = true, features = ["rt-multi-thread"] } -axum = { version = "0.7.5", optional = true } +axum = { version = "0.7.5", features = ["tokio", "http1"], default-features = false, optional = true } tower = { version = "0.4.13", optional = true } tower-http = { version = "0.5", optional = true, features = ["fs"] } thiserror = "1.0.57" -tower-sessions = { version = "0.11", default-features = false } tower-sessions-redis-store = { version = "0.11", optional = true } -async-trait = "0.1.79" +async-trait = { version = "0.1.79", optional = true } axum-login = { version = "0.14.0", optional = true } +server_fn = { version = "0.6.11", features = ["multipart"] } +symphonia = { version = "0.5.4", default-features = false, features = ["mp3"], optional = true } +multer = { version = "3.0.0", optional = true } log = { version = "0.4.21", optional = true } flexi_logger = { version = "0.28.0", optional = true, default-features = false } +web-sys = "0.3.69" [patch.crates-io] gloo-net = { git = "https://github.com/rustwasm/gloo.git", rev = "a823fab7ecc4068e9a28bd669da5eaf3f0a56380" } [features] -csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] -hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] +hydrate = [ + "leptos/hydrate", + "leptos_meta/hydrate", + "leptos_router/hydrate", + "console_error_panic_hook", + "wasm-bindgen", +] ssr = [ "dep:leptos_axum", "leptos/ssr", @@ -56,13 +63,15 @@ ssr = [ "openssl", "diesel_migrations", "pbkdf2", - "futures", "tokio", "axum", "tower", "tower-http", "tower-sessions-redis-store", + "async-trait", "axum-login", + "symphonia", + "multer", "log", "flexi_logger", ] diff --git a/assets/images/placeholders/MusicPlaceholder.svg b/assets/images/placeholders/MusicPlaceholder.svg new file mode 100644 index 0000000..4a3917b --- /dev/null +++ b/assets/images/placeholders/MusicPlaceholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/migrations/2024-05-16-220659_add_album_images/down.sql b/migrations/2024-05-16-220659_add_album_images/down.sql new file mode 100644 index 0000000..118e9b5 --- /dev/null +++ b/migrations/2024-05-16-220659_add_album_images/down.sql @@ -0,0 +1 @@ +ALTER TABLE albums DROP COLUMN image_path; diff --git a/migrations/2024-05-16-220659_add_album_images/up.sql b/migrations/2024-05-16-220659_add_album_images/up.sql new file mode 100644 index 0000000..b9f26dc --- /dev/null +++ b/migrations/2024-05-16-220659_add_album_images/up.sql @@ -0,0 +1 @@ +ALTER TABLE albums ADD COLUMN image_path VARCHAR; diff --git a/migrations/2024-05-18-231505_add_user_admin/down.sql b/migrations/2024-05-18-231505_add_user_admin/down.sql new file mode 100644 index 0000000..8c2de0f --- /dev/null +++ b/migrations/2024-05-18-231505_add_user_admin/down.sql @@ -0,0 +1 @@ +ALTER TABLE users DROP COLUMN admin; diff --git a/migrations/2024-05-18-231505_add_user_admin/up.sql b/migrations/2024-05-18-231505_add_user_admin/up.sql new file mode 100644 index 0000000..58a6e49 --- /dev/null +++ b/migrations/2024-05-18-231505_add_user_admin/up.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN admin BOOLEAN DEFAULT FALSE NOT NULL; diff --git a/src/app.rs b/src/app.rs index 946b4c2..67a6c7a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -14,6 +14,10 @@ pub fn App() -> impl IntoView { // Provides context that manages stylesheets, titles, meta tags, etc. provide_meta_context(); + let play_status = PlayStatus::default(); + let play_status = create_rw_signal(play_status); + let upload_open = create_rw_signal(false); + view! { // injects a stylesheet into the document // id=leptos means cargo-leptos will hot-reload this stylesheet @@ -33,7 +37,11 @@ pub fn App() -> impl IntoView { }>
- + }> + + + + @@ -46,24 +54,17 @@ use crate::components::sidebar::*; use crate::components::dashboard::*; use crate::components::search::*; use crate::components::personal::*; +use crate::components::upload::*; /// Renders the home page of your application. #[component] -fn HomePage() -> impl IntoView { - let play_status = PlayStatus::default(); - let play_status = create_rw_signal(play_status); - - let (dashboard_open, set_dashboard_open) = create_signal(true); - +fn HomePage(play_status: RwSignal, upload_open: RwSignal) -> impl IntoView { view! {
- - } - > - - + + + // This will render the child route components + diff --git a/src/auth.rs b/src/auth.rs index 58ba6f4..37f861f 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -21,9 +21,10 @@ use crate::users::UserCredentials; pub async fn signup(new_user: User) -> Result<(), ServerFnError> { use crate::users::create_user; - // Ensure the user has no id + // Ensure the user has no id, and is not a self-proclaimed admin let new_user = User { id: None, + admin: false, ..new_user }; @@ -143,3 +144,37 @@ pub async fn get_user() -> Result { auth_session.user.ok_or(ServerFnError::::ServerError("User not logged in".to_string())) } + +/// Check if a user is an admin +/// Returns a Result with a boolean indicating if the user is logged in and an admin +#[server(endpoint = "check_admin")] +pub async fn check_admin() -> Result { + let auth_session = extract::>().await + .map_err(|e| ServerFnError::::ServerError(format!("Error getting auth session: {}", e)))?; + + Ok(auth_session.user.as_ref().map(|u| u.admin).unwrap_or(false)) +} + +/// Require that a user is logged in and an admin +/// Returns a Result with the error message if the user is not logged in or is not an admin +/// Intended to be used at the start of a protected route, to ensure the user is logged in and an admin: +/// ```rust +/// use leptos::*; +/// use libretunes::auth::require_admin; +/// #[server(endpoint = "protected_admin_route")] +/// pub async fn protected_admin_route() -> Result<(), ServerFnError> { +/// require_admin().await?; +/// // Continue with protected route +/// Ok(()) +/// } +/// ``` +#[cfg(feature = "ssr")] +pub async fn require_admin() -> Result<(), ServerFnError> { + check_admin().await.and_then(|is_admin| { + if is_admin { + Ok(()) + } else { + Err(ServerFnError::::ServerError(format!("Unauthorized"))) + } + }) +} diff --git a/src/auth_backend.rs b/src/auth_backend.rs index 79e5d9c..cf53a40 100644 --- a/src/auth_backend.rs +++ b/src/auth_backend.rs @@ -1,10 +1,17 @@ -use async_trait::async_trait; use axum_login::{AuthnBackend, AuthUser, UserId}; use crate::users::UserCredentials; use leptos::server_fn::error::ServerFnErrorErr; use crate::models::User; +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "ssr")] { + use async_trait::async_trait; + } +} + impl AuthUser for User { type Id = i32; diff --git a/src/components.rs b/src/components.rs index 4d0c8a5..ede9b31 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,4 +1,5 @@ pub mod sidebar; pub mod dashboard; pub mod search; -pub mod personal; \ No newline at end of file +pub mod personal; +pub mod upload; diff --git a/src/components/sidebar.rs b/src/components/sidebar.rs index 805fa4e..91c3a44 100644 --- a/src/components/sidebar.rs +++ b/src/components/sidebar.rs @@ -1,30 +1,34 @@ use leptos::leptos_dom::*; use leptos::*; use leptos_icons::*; +use crate::components::upload::*; #[component] -pub fn Sidebar(setter: WriteSignal, active: ReadSignal) -> impl IntoView { - let open_dashboard = move |_| { - setter.update(|value| *value = true); - log!("open dashboard"); - }; - let open_search = move |_| { - setter.update(|value| *value = false); - log!("open search"); - }; +pub fn Sidebar(upload_open: RwSignal) -> impl IntoView { + use leptos_router::use_location; + let location = use_location(); + + let on_dashboard = Signal::derive( + move || location.pathname.get().starts_with("/dashboard") || location.pathname.get() == "/", + ); + + let on_search = Signal::derive( + move || location.pathname.get().starts_with("/search"), + ); view! {