diff --git a/AobaClient/assets/Aobax32.ico b/AobaClient/assets/Aobax32.ico new file mode 100644 index 0000000..8cd9cc2 Binary files /dev/null and b/AobaClient/assets/Aobax32.ico differ diff --git a/AobaClient/assets/favicon.ico b/AobaClient/assets/favicon.ico index eed0c09..4c16395 100644 Binary files a/AobaClient/assets/favicon.ico and b/AobaClient/assets/favicon.ico differ diff --git a/AobaClient/assets/style/colors.scss b/AobaClient/assets/style/colors.scss index ce7436e..541b20d 100644 --- a/AobaClient/assets/style/colors.scss +++ b/AobaClient/assets/style/colors.scss @@ -1,8 +1,8 @@ $mainBGColor: #584577; -$featureColor: #CE2D4F; +$featureColor: #ce2d4f; $accentColor: #f0eaf8; $mainTextColor: #eee; $brightTextColor: #fff; $invertTextColor: #222; -$invertBrightTextColor: #000; \ No newline at end of file +$invertBrightTextColor: #000; diff --git a/AobaClient/assets/style/main.scss b/AobaClient/assets/style/main.scss index dde164b..1376250 100644 --- a/AobaClient/assets/style/main.scss +++ b/AobaClient/assets/style/main.scss @@ -26,7 +26,8 @@ body { #content { grid-area: Content; - margin-left: $navBarSize; + padding: 10px; + /* margin-left: $navBarSize; */ } .mediaGrid { @@ -55,6 +56,7 @@ body { text-align: center; width: 100%; display: block; + overflow: hidden; } .details { display: flex; diff --git a/AobaClient/assets/style/mixins.scss b/AobaClient/assets/style/mixins.scss index 64faa4a..a74265a 100644 --- a/AobaClient/assets/style/mixins.scss +++ b/AobaClient/assets/style/mixins.scss @@ -1,4 +1,4 @@ -$navBarSize: 40px; +$navBarSize: 64px; @mixin mobile { @media (max-width: 700px) { @@ -40,4 +40,4 @@ $navBarSize: 40px; @container (max-width: 1200px) { @content; } -} \ No newline at end of file +} diff --git a/AobaClient/assets/style/nav.scss b/AobaClient/assets/style/nav.scss index 67332ab..aea6742 100644 --- a/AobaClient/assets/style/nav.scss +++ b/AobaClient/assets/style/nav.scss @@ -1,6 +1,5 @@ -@import 'mixins'; -@import 'colors'; - +@import "mixins"; +@import "colors"; nav { display: grid; @@ -9,14 +8,20 @@ nav { background-color: $featureColor; height: 100dvh; position: fixed; + width: $navBarSize; - >* { + > * { display: flex; flex-direction: column; } .branding { grid-area: Branding; + + img { + width: $navBarSize; + object-fit: contain; + } } .mainNav { @@ -30,5 +35,4 @@ nav { .utils { grid-area: Utils; } - -} \ No newline at end of file +} diff --git a/AobaClient/src/components/basic/button.rs b/AobaClient/src/components/basic/button.rs index d57fc2b..df7940e 100644 --- a/AobaClient/src/components/basic/button.rs +++ b/AobaClient/src/components/basic/button.rs @@ -19,6 +19,7 @@ pub fn Button(props: ButtonProps) -> Element { rsx! { button { onclick: move |event| { + event.prevent_default(); if let Some(h) = props.onclick { h.call(event); } diff --git a/AobaClient/src/components/basic/input.rs b/AobaClient/src/components/basic/input.rs index 9bc33d6..f00202d 100644 --- a/AobaClient/src/components/basic/input.rs +++ b/AobaClient/src/components/basic/input.rs @@ -3,11 +3,12 @@ use dioxus::prelude::*; #[derive(PartialEq, Clone, Props)] pub struct InputProps { pub r#type: Option, - pub value: Option, + pub value: Option>, pub label: Option, pub placeholder: Option, pub name: String, pub oninput: Option>, + pub required: Option, } #[component] @@ -21,7 +22,8 @@ pub fn Input(props: InputProps) -> Element { type : props.r#type.unwrap_or("text".into()), value: props.value, name: props.name, - placeholder:ph + placeholder:ph, + required: props.required } } } diff --git a/AobaClient/src/components/media_grid.rs b/AobaClient/src/components/media_grid.rs index b3dab4a..066e73d 100644 --- a/AobaClient/src/components/media_grid.rs +++ b/AobaClient/src/components/media_grid.rs @@ -3,6 +3,7 @@ use tonic::{IntoRequest, Request}; use crate::{ components::MediaItem, + contexts::AuthContext, rpc::{aoba::PageFilter, get_rpc_client}, }; @@ -34,11 +35,17 @@ impl Into for MediaGridProps { #[component] pub fn MediaGrid(props: MediaGridProps) -> Element { - let media_result = use_resource(use_reactive!(|(props,)| async move { + let jwt = use_context::().jwt; + let media_result = use_resource(use_reactive!(|(props, jwt)| async move { let mut client = get_rpc_client(); let mut req = Request::new(props.into()); + let token = if jwt.cloned().is_some() { + jwt.unwrap() + } else { + "".into() + }; req.metadata_mut() - .insert("authorization", "Bearer ".parse().unwrap()); + .insert("authorization", format!("Bearer {token}").parse().unwrap()); let result = client.list_media(req).await; return result.expect("Failed to load media").into_inner(); })); diff --git a/AobaClient/src/components/navbar.rs b/AobaClient/src/components/navbar.rs index 5c7a0a7..07b0f1b 100644 --- a/AobaClient/src/components/navbar.rs +++ b/AobaClient/src/components/navbar.rs @@ -3,6 +3,7 @@ use dioxus::prelude::*; use crate::Route; const NAV_CSS: Asset = asset!("/assets/style/nav.scss"); +const NAV_ICON: Asset = asset!("/assets/favicon.ico"); #[component] pub fn Navbar() -> Element { @@ -30,7 +31,10 @@ pub fn MainNaviagation() -> Element { #[component] pub fn Branding() -> Element { rsx! { - div { class: "branding" } + div { class: "branding", + "Aoba" + img {src: NAV_ICON} + } } } diff --git a/AobaClient/src/components/search.rs b/AobaClient/src/components/search.rs index 74b053d..c8d9b06 100644 --- a/AobaClient/src/components/search.rs +++ b/AobaClient/src/components/search.rs @@ -1,15 +1,15 @@ use dioxus::prelude::*; #[component] -pub fn Search(query: Option, oninput: EventHandler) -> Element { +pub fn Search(query: Signal) -> Element { rsx! { div{ class: "searchBar", input { type: "search", placeholder: "Search Files", - value: query.unwrap_or("".into()), - oninput: move |event| oninput.call(event) + value: query, + oninput: move |event| query.set(event.value()) } } } diff --git a/AobaClient/src/contexts/auth_context.rs b/AobaClient/src/contexts/auth_context.rs new file mode 100644 index 0000000..687d5df --- /dev/null +++ b/AobaClient/src/contexts/auth_context.rs @@ -0,0 +1,6 @@ +use dioxus::signals::Signal; + +#[derive(Clone, Copy, Default)] +pub struct AuthContext { + pub jwt: Signal>, +} diff --git a/AobaClient/src/contexts/mod.rs b/AobaClient/src/contexts/mod.rs new file mode 100644 index 0000000..ec17c5a --- /dev/null +++ b/AobaClient/src/contexts/mod.rs @@ -0,0 +1,2 @@ +mod auth_context; +pub use auth_context::*; diff --git a/AobaClient/src/layouts/main_layout.rs b/AobaClient/src/layouts/main_layout.rs index e988238..e3a1b26 100644 --- a/AobaClient/src/layouts/main_layout.rs +++ b/AobaClient/src/layouts/main_layout.rs @@ -1,11 +1,17 @@ use dioxus::prelude::*; -use crate::{components::Navbar, Route}; +use crate::{components::Navbar, contexts::AuthContext, layouts::BasicLayout, views::Login, Route}; #[component] pub fn MainLayout() -> Element { - rsx! { + let auth_context = use_context::(); + + // if auth_context.jwt.cloned().is_none() { + // return rsx! { Login { } }; + // } + + return rsx! { Navbar {} Outlet:: {} - } + }; } diff --git a/AobaClient/src/main.rs b/AobaClient/src/main.rs index 424d125..5097102 100644 --- a/AobaClient/src/main.rs +++ b/AobaClient/src/main.rs @@ -1,10 +1,12 @@ pub mod components; +pub mod contexts; mod layouts; pub mod models; pub mod route; pub mod rpc; pub mod views; +use contexts::AuthContext; use dioxus::prelude::*; use route::Route; @@ -17,6 +19,7 @@ fn main() { #[component] fn App() -> Element { + let _auth_state = use_context_provider(|| AuthContext::default()); rsx! { document::Link { rel: "icon", href: FAVICON } document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" } @@ -26,6 +29,6 @@ fn App() -> Element { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap", } - Router:: {} + Router:: { } } } diff --git a/AobaClient/src/route.rs b/AobaClient/src/route.rs index 0e91262..917d9b9 100644 --- a/AobaClient/src/route.rs +++ b/AobaClient/src/route.rs @@ -1,6 +1,6 @@ use crate::{ - layouts::{BasicLayout, MainLayout}, - views::{Home, Login, Settings}, + layouts::MainLayout, + views::{Home, Settings}, }; use dioxus::prelude::*; @@ -12,8 +12,5 @@ pub enum Route { Home {}, #[route("/settings")] Settings {}, - #[end_layout] - #[layout(BasicLayout)] - #[route("/login")] - Login {}, + // #[end_layout] } diff --git a/AobaClient/src/rpc.rs b/AobaClient/src/rpc.rs index 6200b61..89b15f7 100644 --- a/AobaClient/src/rpc.rs +++ b/AobaClient/src/rpc.rs @@ -1,32 +1,42 @@ use std::sync::RwLock; -use aoba::aoba_rpc_client::AobaRpcClient; +use aoba::{aoba_rpc_client::AobaRpcClient, auth_rpc_client::AuthRpcClient}; use tonic_web_wasm_client::Client; pub mod aoba { tonic::include_proto!("aoba"); + tonic::include_proto!("aoba.auth"); } +const HOST: &'static str = "http://localhost:5164"; + static RPC_CLIENT: RpcConnection = RpcConnection { - client: RwLock::new(None), + aoba: RwLock::new(None), + auth: RwLock::new(None), }; #[derive(Default)] pub struct RpcConnection { - client: RwLock>>, + aoba: RwLock>>, + auth: RwLock>>, } impl RpcConnection { pub fn get_client(&self) -> AobaRpcClient { self.ensure_client(); - return self.client.read().unwrap().clone().unwrap(); + return self.aoba.read().unwrap().clone().unwrap(); + } + + pub fn get_auth_client(&self) -> AuthRpcClient { + self.ensure_client(); + return self.auth.read().unwrap().clone().unwrap(); } fn ensure_client(&self) { - if self.client.read().unwrap().is_none() { - let wasm_client = Client::new("http://localhost:5164".into()); - let c = AobaRpcClient::new(wasm_client); - *self.client.write().unwrap() = Some(c); + if self.aoba.read().unwrap().is_none() { + let wasm_client = Client::new(HOST.into()); + *self.aoba.write().unwrap() = Some(AobaRpcClient::new(wasm_client.clone())); + *self.auth.write().unwrap() = Some(AuthRpcClient::new(wasm_client.clone())); } } } @@ -34,3 +44,7 @@ impl RpcConnection { pub fn get_rpc_client() -> AobaRpcClient { return RPC_CLIENT.get_client(); } + +pub fn get_auth_rpc_client() -> AuthRpcClient { + return RPC_CLIENT.get_auth_client(); +} diff --git a/AobaClient/src/views/home.rs b/AobaClient/src/views/home.rs index 14e55df..3043eb8 100644 --- a/AobaClient/src/views/home.rs +++ b/AobaClient/src/views/home.rs @@ -3,16 +3,13 @@ use dioxus::prelude::*; #[component] pub fn Home() -> Element { - let mut query = use_signal(|| "".to_string()); + let query = use_signal(|| "".to_string()); rsx! { div { id: "content", Search { - query: query.cloned(), - oninput: move |event:FormEvent| { - query.set(event.value()) - } + query: query }, MediaGrid { query: query.cloned() } } diff --git a/AobaClient/src/views/login.rs b/AobaClient/src/views/login.rs index 1961c51..d96e21f 100644 --- a/AobaClient/src/views/login.rs +++ b/AobaClient/src/views/login.rs @@ -1,16 +1,58 @@ use dioxus::prelude::*; +use tonic::IntoRequest; -use crate::components::basic::{Button, Input}; +use crate::{ + components::basic::{Button, Input}, + contexts::AuthContext, + rpc::{aoba::Credentials, get_auth_rpc_client}, +}; #[component] pub fn Login() -> Element { + let username = use_signal(|| "".to_string()); + let password = use_signal(|| "".to_string()); + let mut auth_context = use_context::(); + + let onclick = move |_| { + spawn(async move { + let mut auth = get_auth_rpc_client(); + let result = auth + .login( + Credentials { + user: username.cloned(), + password: password.cloned(), + } + .into_request(), + ) + .await; + match result { + Ok(res) => { + match res.into_inner().result.unwrap() { + crate::rpc::aoba::login_response::Result::Jwt(jwt) => { + auth_context.jwt.set(Some(jwt.token)); + } + crate::rpc::aoba::login_response::Result::Error(_login_error) => { + auth_context.jwt.set(None); + } + }; + } + Err(_err) => { + auth_context.jwt.set(None); + } + } + }); + }; + rsx! { div{ id: "centralModal", form{ - Input { type : "text", name: "username", label: "Username" }, - Input { type : "password", name: "password", label: "Password" }, - Button{text: "Login!"} + Input { type : "text", name: "username", label: "Username", value: username, required: true }, + Input { type : "password", name: "password", label: "Password", value: password, required: true }, + Button { + text: "Login!", + onclick: onclick + } } } } diff --git a/AobaCore/AobaService.cs b/AobaCore/AobaService.cs index 561655e..b3f54a3 100644 --- a/AobaCore/AobaService.cs +++ b/AobaCore/AobaService.cs @@ -18,7 +18,7 @@ public class AobaService(IMongoDatabase db) return await _media.Find(m => m.Id == id).FirstOrDefaultAsync(cancellationToken); } - public async Task> FindMediaAsync(string? query, int page = 1, int pageSize = 50) + public async Task> FindMediaAsync(string? query, int page = 1, int pageSize = 100) { var filter = string.IsNullOrWhiteSpace(query) ? "{}" : Builders.Filter.Text(query); var find = _media.Find(filter); diff --git a/AobaServer/Services/AobaRpcService.cs b/AobaServer/Services/AobaRpcService.cs index e448a72..c60ef85 100644 --- a/AobaServer/Services/AobaRpcService.cs +++ b/AobaServer/Services/AobaRpcService.cs @@ -18,7 +18,7 @@ public class AobaRpcService(AobaService aobaService) : AobaRpc.AobaRpcBase public override async Task ListMedia(PageFilter request, ServerCallContext context) { - var result = await aobaService.FindMediaAsync(request.Query, request.HasPage ? request.Page : 1, request.HasPageSize ? request.PageSize : 50); + var result = await aobaService.FindMediaAsync(request.Query, request.HasPage ? request.Page : 1, request.HasPageSize ? request.PageSize : 100); return result.ToResponse(); }