Auth rpc
Search Bar Search requests
This commit is contained in:
@@ -2,7 +2,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
tonic_build::configure()
|
tonic_build::configure()
|
||||||
.build_server(false)
|
.build_server(false)
|
||||||
.build_client(true)
|
.build_client(true)
|
||||||
.compile_protos(&["..\\AobaServer\\Proto\\Aoba.proto"], &["..\\AobaServer\\Proto\\"])?;
|
.compile_protos(
|
||||||
|
&["..\\AobaServer\\Proto\\Aoba.proto", "..\\AobaServer\\Proto\\Auth.proto"],
|
||||||
|
&["..\\AobaServer\\Proto\\"],
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use dioxus::prelude::*;
|
|||||||
|
|
||||||
#[derive(PartialEq, Clone, Props)]
|
#[derive(PartialEq, Clone, Props)]
|
||||||
pub struct ButtonProps {
|
pub struct ButtonProps {
|
||||||
variant: Option<ButtonVariant>,
|
pub variant: Option<ButtonVariant>,
|
||||||
text: String,
|
pub text: String,
|
||||||
|
pub onclick: Option<EventHandler<Event<MouseData>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone)]
|
||||||
@@ -16,6 +17,13 @@ pub enum ButtonVariant {
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn Button(props: ButtonProps) -> Element {
|
pub fn Button(props: ButtonProps) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
button { "{props.text}" }
|
button {
|
||||||
|
onclick: move |event| {
|
||||||
|
if let Some(h) = props.onclick {
|
||||||
|
h.call(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"{props.text}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub struct InputProps {
|
|||||||
pub label: Option<String>,
|
pub label: Option<String>,
|
||||||
pub placeholder: Option<String>,
|
pub placeholder: Option<String>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub oninput: Option<EventHandler<FormEvent>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use tonic::{metadata::MetadataValue, IntoRequest, Request};
|
use tonic::{IntoRequest, Request};
|
||||||
|
|
||||||
use crate::rpc::{
|
use crate::{
|
||||||
aoba::{MediaModel, PageFilter},
|
components::MediaItem,
|
||||||
get_rpc_client,
|
rpc::{aoba::PageFilter, get_rpc_client},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Props)]
|
#[derive(PartialEq, Clone, Props)]
|
||||||
pub struct MediaGridProps {
|
pub struct MediaGridProps {
|
||||||
pub query: Option<String>,
|
pub query: Option<String>,
|
||||||
|
#[props(default = Some(1))]
|
||||||
pub page: Option<i32>,
|
pub page: Option<i32>,
|
||||||
|
#[props(default = Some(100))]
|
||||||
pub page_size: Option<i32>,
|
pub page_size: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,14 +34,14 @@ impl Into<PageFilter> for MediaGridProps {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn MediaGrid(props: MediaGridProps) -> Element {
|
pub fn MediaGrid(props: MediaGridProps) -> Element {
|
||||||
let media_result = use_resource(|| async move {
|
let media_result = use_resource(use_reactive!(|(props,)| async move {
|
||||||
let mut client = get_rpc_client();
|
let mut client = get_rpc_client();
|
||||||
let mut req = Request::new(PageFilter::default());
|
let mut req = Request::new(props.into());
|
||||||
req.metadata_mut()
|
req.metadata_mut()
|
||||||
.insert("authorization", "Bearer <toto: get token>".parse().unwrap());
|
.insert("authorization", "Bearer <toto: get token>".parse().unwrap());
|
||||||
let result = client.list_media(req).await;
|
let result = client.list_media(req).await;
|
||||||
return result.expect("Failed to load media").into_inner();
|
return result.expect("Failed to load media").into_inner();
|
||||||
});
|
}));
|
||||||
|
|
||||||
match &*media_result.read_unchecked() {
|
match &*media_result.read_unchecked() {
|
||||||
Some(result) => rsx! {
|
Some(result) => rsx! {
|
||||||
@@ -49,53 +49,16 @@ pub fn MediaGrid(props: MediaGridProps) -> Element {
|
|||||||
class: "mediaGrid",
|
class: "mediaGrid",
|
||||||
{result.items.iter().map(|itm| rsx!{
|
{result.items.iter().map(|itm| rsx!{
|
||||||
MediaItem { item: itm.clone() }
|
MediaItem { item: itm.clone() }
|
||||||
})}
|
})},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => rsx!(),
|
None => rsx! {
|
||||||
}
|
div{
|
||||||
// let items = media_result..unwrap().items;
|
class: "mediaGrid",
|
||||||
// rsx! {
|
div {
|
||||||
// div{
|
"No results could be loaded"
|
||||||
// class: "mediaGrid",
|
|
||||||
// {items.iter().map(|itm| rsx!{
|
|
||||||
// MediaItem { item: itm.clone() }
|
|
||||||
// })}
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Props)]
|
|
||||||
pub struct MediaItemProps {
|
|
||||||
pub item: MediaModel,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn MediaItem(props: MediaItemProps) -> Element {
|
|
||||||
let filename = props.item.file_name;
|
|
||||||
let id = props.item.id.unwrap().value;
|
|
||||||
let mtype = props.item.media_type.to_string();
|
|
||||||
// let url = "https://aoba.app/i/{}";
|
|
||||||
rsx! {
|
|
||||||
div{
|
|
||||||
class: "mediaItem",
|
|
||||||
img{ src: "https://aoba.app/i/{id}" }
|
|
||||||
div {
|
|
||||||
class: "info",
|
|
||||||
span{
|
|
||||||
class: "name",
|
|
||||||
"{filename}"
|
|
||||||
},
|
|
||||||
div{
|
|
||||||
class: "details",
|
|
||||||
span{
|
|
||||||
"{mtype}"
|
|
||||||
},
|
|
||||||
span{
|
|
||||||
"{props.item.view_count}"
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
AobaClient/src/components/media_item.rs
Normal file
43
AobaClient/src/components/media_item.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
use crate::rpc::aoba::MediaModel;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Props)]
|
||||||
|
pub struct MediaItemProps {
|
||||||
|
pub item: MediaModel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn MediaItem(props: MediaItemProps) -> Element {
|
||||||
|
let mtype = props.item.media_type().as_str_name();
|
||||||
|
let filename = props.item.file_name;
|
||||||
|
let id = props.item.media_id.unwrap().value;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let src = format!("http://localhost:5164/m/{id}");
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let src = format!("https://aoba.app/m/{id}");
|
||||||
|
// let url = "https://aoba.app/i/{}";
|
||||||
|
rsx! {
|
||||||
|
div{
|
||||||
|
class: "mediaItem",
|
||||||
|
img{ src: src }
|
||||||
|
div {
|
||||||
|
class: "info",
|
||||||
|
span{
|
||||||
|
class: "name",
|
||||||
|
"{filename}"
|
||||||
|
},
|
||||||
|
div{
|
||||||
|
class: "details",
|
||||||
|
span{
|
||||||
|
"{mtype}"
|
||||||
|
},
|
||||||
|
span{
|
||||||
|
"{props.item.view_count}"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
pub mod basic;
|
pub mod basic;
|
||||||
mod media_grid;
|
mod media_grid;
|
||||||
|
mod media_item;
|
||||||
mod navbar;
|
mod navbar;
|
||||||
mod search;
|
mod search;
|
||||||
pub use media_grid::*;
|
pub use media_grid::*;
|
||||||
|
pub use media_item::*;
|
||||||
pub use navbar::*;
|
pub use navbar::*;
|
||||||
pub use search::*;
|
pub use search::*;
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Search() -> Element {
|
pub fn Search(query: Option<String>, oninput: EventHandler<FormEvent>) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div{
|
div{
|
||||||
class: "searchBar",
|
class: "searchBar",
|
||||||
input {
|
input {
|
||||||
type: "search",
|
type: "search",
|
||||||
placeholder: "Search Files"
|
placeholder: "Search Files",
|
||||||
|
value: query.unwrap_or("".into()),
|
||||||
|
oninput: move |event| oninput.call(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,18 @@ use dioxus::prelude::*;
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Home() -> Element {
|
pub fn Home() -> Element {
|
||||||
|
let mut query = use_signal(|| "".to_string());
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div { id: "content",
|
div {
|
||||||
Search { },
|
id: "content",
|
||||||
MediaGrid { }
|
Search {
|
||||||
|
query: query.cloned(),
|
||||||
|
oninput: move |event:FormEvent| {
|
||||||
|
query.set(event.value())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MediaGrid { query: query.cloned() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
|
||||||
<PackageReference Include="MaybeError" Version="1.1.0" />
|
<PackageReference Include="MaybeError" Version="1.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
|
||||||
<PackageReference Include="MongoDB.Analyzer" Version="1.5.0" />
|
<PackageReference Include="MongoDB.Analyzer" Version="1.5.0" />
|
||||||
<PackageReference Include="MongoDB.Driver" Version="3.3.0" />
|
<PackageReference Include="MongoDB.Driver" Version="3.3.0" />
|
||||||
<PackageReference Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.0.0" />
|
<PackageReference Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.0.0" />
|
||||||
|
|||||||
26
AobaCore/AobaIndexCreationService.cs
Normal file
26
AobaCore/AobaIndexCreationService.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using AobaCore.Models;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
using MongoDB.Driver;
|
||||||
|
|
||||||
|
namespace AobaCore;
|
||||||
|
|
||||||
|
public class AobaIndexCreationService(IMongoDatabase db): BackgroundService
|
||||||
|
{
|
||||||
|
private readonly IMongoCollection<Media> _media = db.GetCollection<Media>("media");
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
var textKeys = Builders<Media>.IndexKeys
|
||||||
|
.Text(m => m.Filename);
|
||||||
|
|
||||||
|
var textModel = new CreateIndexModel<Media>(textKeys, new CreateIndexOptions
|
||||||
|
{
|
||||||
|
Name = "Text",
|
||||||
|
Background = true
|
||||||
|
});
|
||||||
|
|
||||||
|
await _media.EnsureIndexAsync(textModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,20 @@ public static class Extensions
|
|||||||
services.AddSingleton(dbClient);
|
services.AddSingleton(dbClient);
|
||||||
services.AddSingleton<IMongoDatabase>(db);
|
services.AddSingleton<IMongoDatabase>(db);
|
||||||
services.AddSingleton<AobaService>();
|
services.AddSingleton<AobaService>();
|
||||||
|
services.AddHostedService<AobaIndexCreationService>();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task EnsureIndexAsync<T>(this IMongoCollection<T> collection, CreateIndexModel<T> indexModel)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await collection.Indexes.CreateOneAsync(indexModel);
|
||||||
|
}
|
||||||
|
catch (MongoCommandException e) when (e.Code == 85 || e.Code == 86) //CodeName "IndexOptionsConflict" or "NameConflict"
|
||||||
|
{
|
||||||
|
await collection.Indexes.DropOneAsync(indexModel.Options.Name);
|
||||||
|
await collection.Indexes.CreateOneAsync(indexModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Protobuf Include="Proto\Aoba.proto"></Protobuf>
|
<Protobuf Include="Proto\Aoba.proto"></Protobuf>
|
||||||
|
<Protobuf Include="Proto\Auth.proto"></Protobuf>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -16,16 +16,12 @@ public class MediaController(AobaService aobaService, ILogger<MediaController> l
|
|||||||
[ResponseCache(Duration = int.MaxValue)]
|
[ResponseCache(Duration = int.MaxValue)]
|
||||||
public async Task<IActionResult> MediaAsync(ObjectId id, [FromServices] MongoClient client, CancellationToken cancellationToken)
|
public async Task<IActionResult> MediaAsync(ObjectId id, [FromServices] MongoClient client, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using var session = await client.StartSessionAsync(cancellationToken: cancellationToken);
|
|
||||||
session.StartTransaction();
|
|
||||||
var file = await aobaService.GetFileStreamAsync(id, cancellationToken: cancellationToken);
|
var file = await aobaService.GetFileStreamAsync(id, cancellationToken: cancellationToken);
|
||||||
if (file.HasError)
|
if (file.HasError)
|
||||||
{
|
{
|
||||||
await session.AbortTransactionAsync(cancellationToken: cancellationToken);
|
|
||||||
logger.LogError(file.Error.Exception, "Failed to load media stream");
|
logger.LogError(file.Error.Exception, "Failed to load media stream");
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
await session.CommitTransactionAsync(cancellationToken: cancellationToken);
|
|
||||||
var mime = MimeTypesMap.GetMimeType(file.Value.FileInfo.Filename);
|
var mime = MimeTypesMap.GetMimeType(file.Value.FileInfo.Filename);
|
||||||
_ = aobaService.IncrementFileViewCountAsync(id, cancellationToken);
|
_ = aobaService.IncrementFileViewCountAsync(id, cancellationToken);
|
||||||
return File(file, mime, true);
|
return File(file, mime, true);
|
||||||
|
|||||||
@@ -42,7 +42,16 @@ builder.Services.AddCors(o =>
|
|||||||
p.AllowAnyOrigin();
|
p.AllowAnyOrigin();
|
||||||
p.AllowAnyMethod();
|
p.AllowAnyMethod();
|
||||||
p.AllowAnyHeader();
|
p.AllowAnyHeader();
|
||||||
//p.WithOrigins("http://127.0.0.1:8080", "https://aoba.app");
|
});
|
||||||
|
o.AddPolicy("RPC", p =>
|
||||||
|
{
|
||||||
|
p.AllowAnyMethod();
|
||||||
|
p.AllowAnyHeader();
|
||||||
|
#if DEBUG
|
||||||
|
p.AllowAnyOrigin();
|
||||||
|
#else
|
||||||
|
p.WithOrigins("http://127.0.0.1:8080", "https://aoba.app");
|
||||||
|
#endif
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,7 +116,10 @@ app.UseAuthorization();
|
|||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.MapObserability();
|
app.MapObserability();
|
||||||
app.MapGrpcService<AobaRpcService>()
|
app.MapGrpcService<AobaRpcService>()
|
||||||
.RequireCors("AllowAll");
|
.RequireCors("RPC");
|
||||||
|
app.MapGrpcService<AobaAuthService>()
|
||||||
|
.AllowAnonymous()
|
||||||
|
.RequireCors("RPC");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
33
AobaServer/Proto/Auth.proto
Normal file
33
AobaServer/Proto/Auth.proto
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "Aoba.RPC.Auth";
|
||||||
|
package aoba.Auth;
|
||||||
|
|
||||||
|
service AuthRpc {
|
||||||
|
rpc Login(Credentials) returns (LoginResponse);
|
||||||
|
rpc LoginPasskey(PassKeyPayload) returns (LoginResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message Credentials{
|
||||||
|
string user = 1;
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PassKeyPayload {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message Jwt{
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginResponse{
|
||||||
|
oneof result {
|
||||||
|
Jwt jwt = 1;
|
||||||
|
LoginError error = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginError{
|
||||||
|
string message = 1;
|
||||||
|
}
|
||||||
6
AobaServer/Services/AobaAuthService.cs
Normal file
6
AobaServer/Services/AobaAuthService.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace AobaServer.Services;
|
||||||
|
|
||||||
|
public class AobaAuthService() : Aoba.RPC.Auth.AuthRpc.AuthRpcBase
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
|
||||||
namespace AobaServer.Utils;
|
namespace AobaServer.Utils;
|
||||||
|
|
||||||
@@ -12,4 +13,6 @@ public static class Extensions
|
|||||||
return result;
|
return result;
|
||||||
return ObjectId.Empty;
|
return ObjectId.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user