diff --git a/AobaClient/assets/style/inputs.scss b/AobaClient/assets/style/inputs.scss index 92211a1..f0ea42f 100644 --- a/AobaClient/assets/style/inputs.scss +++ b/AobaClient/assets/style/inputs.scss @@ -21,3 +21,8 @@ input[type="text"] { border-radius: 20px; } } + +textarea { + min-height: 200px; + min-width: 500px; +} diff --git a/AobaClient/assets/style/main.scss b/AobaClient/assets/style/main.scss index 7390af7..245c42d 100644 --- a/AobaClient/assets/style/main.scss +++ b/AobaClient/assets/style/main.scss @@ -30,6 +30,7 @@ body { #content { grid-area: Content; + overflow-x: hidden; padding: 10px; /* margin-left: $navBarSize; */ } @@ -106,3 +107,11 @@ form { align-self: center; } } + +.codeSelect { + line-break: anywhere; + white-space: pre-wrap; + background-color: $featureColor; + padding: 5px; + user-select: all; +} diff --git a/AobaClient/src/components/media_grid.rs b/AobaClient/src/components/media_grid.rs index 066e73d..0f95b56 100644 --- a/AobaClient/src/components/media_grid.rs +++ b/AobaClient/src/components/media_grid.rs @@ -1,9 +1,8 @@ use dioxus::prelude::*; -use tonic::{IntoRequest, Request}; +use tonic::IntoRequest; use crate::{ components::MediaItem, - contexts::AuthContext, rpc::{aoba::PageFilter, get_rpc_client}, }; @@ -35,18 +34,9 @@ impl Into for MediaGridProps { #[component] pub fn MediaGrid(props: MediaGridProps) -> Element { - let jwt = use_context::().jwt; - let media_result = use_resource(use_reactive!(|(props, jwt)| async move { + let media_result = use_resource(use_reactive!(|(props)| 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", format!("Bearer {token}").parse().unwrap()); - let result = client.list_media(req).await; + let result = client.list_media(props.into_request()).await; return result.expect("Failed to load media").into_inner(); })); diff --git a/AobaClient/src/contexts/auth_context.rs b/AobaClient/src/contexts/auth_context.rs index c4b4aaf..5bd5b87 100644 --- a/AobaClient/src/contexts/auth_context.rs +++ b/AobaClient/src/contexts/auth_context.rs @@ -1,6 +1,8 @@ use dioxus::signals::{Signal, Writable}; use web_sys::window; +use crate::rpc::{login, logout}; + #[derive(Clone, Copy, Default)] pub struct AuthContext { pub jwt: Signal>, @@ -11,12 +13,14 @@ impl AuthContext { self.jwt.set(Some(token.clone())); let local_storage = window().unwrap().local_storage().unwrap().unwrap(); _ = local_storage.set_item("token", token.as_str()); + login(token.clone()); } pub fn logout(&mut self) { self.jwt.set(None); let local_storage = window().unwrap().local_storage().unwrap().unwrap(); _ = local_storage.remove_item("token"); + logout(); } pub fn new() -> Self { @@ -25,17 +29,14 @@ impl AuthContext { match local_storage.get_item("token") { Ok(value) => { if let Some(jwt) = value { - println!("jwt"); + login(jwt.clone()); return AuthContext { jwt: Signal::new(Some(jwt)), }; } return AuthContext::default(); } - Err(_) => { - println!("err"); - AuthContext::default() - } + Err(_) => AuthContext::default(), } } } diff --git a/AobaClient/src/layouts/main_layout.rs b/AobaClient/src/layouts/main_layout.rs index a910250..b775ebb 100644 --- a/AobaClient/src/layouts/main_layout.rs +++ b/AobaClient/src/layouts/main_layout.rs @@ -1,6 +1,6 @@ use dioxus::prelude::*; -use crate::{components::Navbar, contexts::AuthContext, views::Login, Route}; +use crate::{Route, components::Navbar, contexts::AuthContext, views::Login}; #[component] pub fn MainLayout() -> Element { @@ -12,6 +12,9 @@ pub fn MainLayout() -> Element { return rsx! { Navbar {} - Outlet:: {} + div{ + id: "content", + Outlet:: {} + } }; } diff --git a/AobaClient/src/rpc.rs b/AobaClient/src/rpc.rs index d50015a..dbe0594 100644 --- a/AobaClient/src/rpc.rs +++ b/AobaClient/src/rpc.rs @@ -1,6 +1,7 @@ use std::sync::RwLock; use aoba::{aoba_rpc_client::AobaRpcClient, auth_rpc_client::AuthRpcClient}; +use tonic::service::{Interceptor, interceptor::InterceptedService}; use tonic_web_wasm_client::Client; use crate::HOST; @@ -13,16 +14,18 @@ pub mod aoba { static RPC_CLIENT: RpcConnection = RpcConnection { aoba: RwLock::new(None), auth: RwLock::new(None), + jwt: RwLock::new(None), }; #[derive(Default)] pub struct RpcConnection { - aoba: RwLock>>, + aoba: RwLock>>>, auth: RwLock>>, + jwt: RwLock>, } impl RpcConnection { - pub fn get_client(&self) -> AobaRpcClient { + pub fn get_client(&self) -> AobaRpcClient> { self.ensure_client(); return self.aoba.read().unwrap().clone().unwrap(); } @@ -35,16 +38,38 @@ impl RpcConnection { fn ensure_client(&self) { if self.aoba.read().unwrap().is_none() { let wasm_client = Client::new(HOST.into()); - *self.aoba.write().unwrap() = Some(AobaRpcClient::new(wasm_client.clone())); + let aoba_client = AobaRpcClient::with_interceptor(wasm_client.clone(), AuthInterceptor); + *self.aoba.write().unwrap() = Some(aoba_client); *self.auth.write().unwrap() = Some(AuthRpcClient::new(wasm_client.clone())); } } } -pub fn get_rpc_client() -> AobaRpcClient { +#[derive(Clone)] +pub struct AuthInterceptor; +impl Interceptor for AuthInterceptor { + fn call(&mut self, mut request: tonic::Request<()>) -> Result, tonic::Status> { + if let Some(jwt) = RPC_CLIENT.jwt.read().unwrap().clone() { + request + .metadata_mut() + .insert("authorization", format!("Bearer {jwt}").parse().unwrap()); + } + return Ok(request); + } +} + +pub fn get_rpc_client() -> AobaRpcClient> { return RPC_CLIENT.get_client(); } pub fn get_auth_rpc_client() -> AuthRpcClient { return RPC_CLIENT.get_auth_client(); } + +pub fn login(jwt: String) { + *RPC_CLIENT.jwt.write().unwrap() = Some(jwt); +} + +pub fn logout() { + *RPC_CLIENT.jwt.write().unwrap() = None; +} diff --git a/AobaClient/src/views/home.rs b/AobaClient/src/views/home.rs index 3043eb8..8f90220 100644 --- a/AobaClient/src/views/home.rs +++ b/AobaClient/src/views/home.rs @@ -6,12 +6,9 @@ pub fn Home() -> Element { let query = use_signal(|| "".to_string()); rsx! { - div { - id: "content", - Search { - query: query - }, - MediaGrid { query: query.cloned() } - } + Search { + query: query + }, + MediaGrid { query: query.cloned() } } } diff --git a/AobaClient/src/views/settings.rs b/AobaClient/src/views/settings.rs index c0daca9..c02ae42 100644 --- a/AobaClient/src/views/settings.rs +++ b/AobaClient/src/views/settings.rs @@ -1,6 +1,34 @@ use dioxus::prelude::*; +use crate::rpc::get_rpc_client; + #[component] pub fn Settings() -> Element { - rsx! { "this is settings" } + let dst = use_resource(async move || { + let result = get_rpc_client().get_share_x_destination(()).await; + if let Ok(d) = result { + if let Some(r) = d.into_inner().dst_result { + return match r { + crate::rpc::aoba::share_x_response::DstResult::Destination(json) => json, + crate::rpc::aoba::share_x_response::DstResult::Error(err) => err, + }; + } + return "No Result".to_string(); + } + let err = result.err().unwrap(); + let status = err.message(); + return format!("Failed to load config: {status}").to_string(); + }); + + let d = dst.cloned().unwrap_or("".to_string()); + + rsx! { + "this is settings" + div { + pre { + class: "codeSelect", + "test {d}" + } + } + } } diff --git a/AobaServer/Models/ShareXDestination.cs b/AobaServer/Models/ShareXDestination.cs index 2be4bfa..6bc381b 100644 --- a/AobaServer/Models/ShareXDestination.cs +++ b/AobaServer/Models/ShareXDestination.cs @@ -2,7 +2,7 @@ public class ShareXDestination { - public string Version { get; set; } = "13.1.0"; + public string Version { get; set; } = "14.0.1"; public string Name { get; set; } = "Aoba"; public string DestinationType { get; set; } = "ImageUploader, TextUploader, FileUploader"; public string RequestMethod { get; set; } = "POST"; @@ -13,6 +13,6 @@ public class ShareXDestination public string FileFormName { get; set; } = "file"; public string[] RegexList { get; set; } = ["([^/]+)/?$"]; public string URL { get; set; } = "https://aoba.app$json:url$"; - public required string ThumbnailURL { get; set; } - public required string DeletionURL { get; set; } + public string? ThumbnailURL { get; set; } + public string? DeletionURL { get; set; } } \ No newline at end of file diff --git a/AobaServer/Program.cs b/AobaServer/Program.cs index 477f2b5..e49663b 100644 --- a/AobaServer/Program.cs +++ b/AobaServer/Program.cs @@ -109,7 +109,6 @@ if (!app.Environment.IsDevelopment()) } app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); -app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); @@ -123,6 +122,7 @@ app.UseAuthorization(); app.MapControllers(); app.MapObserability(); app.MapGrpcService() + .RequireAuthorization() .RequireCors("RPC"); app.MapGrpcService() .AllowAnonymous() diff --git a/AobaServer/Proto/Aoba.proto b/AobaServer/Proto/Aoba.proto index 79f9959..1190de4 100644 --- a/AobaServer/Proto/Aoba.proto +++ b/AobaServer/Proto/Aoba.proto @@ -1,14 +1,16 @@ syntax = "proto3"; +import "google/protobuf/empty.proto"; option csharp_namespace = "Aoba.RPC"; package aoba; service AobaRpc { rpc GetMedia (Id) returns (MediaResponse); - rpc DeleteMedia (Id) returns (Empty); - rpc UpdateMedia (Empty) returns (Empty); + rpc DeleteMedia (Id) returns (google.protobuf.Empty); + rpc UpdateMedia (google.protobuf.Empty) returns (google.protobuf.Empty); rpc ListMedia(PageFilter) returns (ListResponse); rpc GetUser(Id) returns (UserResponse); + rpc GetShareXDestination(google.protobuf.Empty) returns (ShareXResponse); } message PageFilter { @@ -24,7 +26,7 @@ message Id { message MediaResponse { oneof result { MediaModel value = 1; - Empty empty = 2; + google.protobuf.Empty empty = 2; } } @@ -44,7 +46,7 @@ message Pagination { message UserResponse { oneof userResult { UserModel user = 1; - Empty empty = 2; + google.protobuf.Empty empty = 2; } } @@ -55,7 +57,6 @@ message UserModel { bool isAdmin = 4; } -message Empty {} message MediaModel { Id id = 1; @@ -67,7 +68,7 @@ message MediaModel { Id owner = 7; } -enum MediaType{ +enum MediaType { Image = 0; Audio = 1; Video = 2; @@ -75,3 +76,10 @@ enum MediaType{ Code = 4; Raw = 5; } + +message ShareXResponse { + oneof dstResult { + string destination = 1; + string error = 2; + } +} diff --git a/AobaServer/Services/AobaRpcService.cs b/AobaServer/Services/AobaRpcService.cs index c60ef85..607d7c4 100644 --- a/AobaServer/Services/AobaRpcService.cs +++ b/AobaServer/Services/AobaRpcService.cs @@ -1,14 +1,24 @@ -using AobaCore; +using Aoba.RPC; -using Aoba.RPC; +using AobaCore; +using AobaServer.Models; using AobaServer.Utils; +using Google.Protobuf.WellKnownTypes; + using Grpc.Core; +using Microsoft.AspNetCore.Mvc; + +using MongoDB.Bson.IO; + +using System.Text.Json; +using System.Text.Json.Serialization; + namespace AobaServer.Services; -public class AobaRpcService(AobaService aobaService) : AobaRpc.AobaRpcBase +public class AobaRpcService(AobaService aobaService, AccountsService accountsService, AuthInfo authInfo) : AobaRpc.AobaRpcBase { public override async Task GetMedia(Id request, ServerCallContext context) { @@ -22,4 +32,29 @@ public class AobaRpcService(AobaService aobaService) : AobaRpc.AobaRpcBase return result.ToResponse(); } -} + public override async Task GetShareXDestination(Empty request, ServerCallContext context) + { + var userId = context.GetHttpContext().User.GetId(); + var user = await accountsService.GetUserAsync(userId, context.CancellationToken); + if (user == null) + return new ShareXResponse { Error = "User does not exist" }; + var token = user.GetToken(authInfo); + var dest = new ShareXDestination + { + DeletionURL = string.Empty, + ThumbnailURL = string.Empty, + Headers = new() + { + { "Authorization", $"Bearer {token}" } + } + }; + return new ShareXResponse + { + Destination = JsonSerializer.Serialize(dest, new JsonSerializerOptions + { + WriteIndented = true + }) + }; + } + +} \ No newline at end of file diff --git a/AobaServer/Utils/Extensions.cs b/AobaServer/Utils/Extensions.cs index 9ef2502..fe3856c 100644 --- a/AobaServer/Utils/Extensions.cs +++ b/AobaServer/Utils/Extensions.cs @@ -2,6 +2,8 @@ using AobaServer.Models; +using Grpc.Core; + using Microsoft.IdentityModel.Tokens; using MongoDB.Bson; @@ -37,4 +39,9 @@ public static class Extensions { return user.FindFirstValue(ClaimTypes.NameIdentifier).ToObjectId(); } + + public static ObjectId GetUserId(this ServerCallContext context) + { + return context.GetHttpContext().User.GetId(); + } } diff --git a/AobaServer/Utils/ProtoExtensions.cs b/AobaServer/Utils/ProtoExtensions.cs index e6013ba..4fbb026 100644 --- a/AobaServer/Utils/ProtoExtensions.cs +++ b/AobaServer/Utils/ProtoExtensions.cs @@ -2,6 +2,7 @@ using Aoba.RPC; using MongoDB.Bson; +using Google.Protobuf.WellKnownTypes; namespace AobaServer.Utils;