diff --git a/AobaClient/Cargo.lock b/AobaClient/Cargo.lock index d085629..e45b868 100644 --- a/AobaClient/Cargo.lock +++ b/AobaClient/Cargo.lock @@ -26,13 +26,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "aoba-client" version = "0.1.0" dependencies = [ "dioxus", + "prost", "serde", "serde_repr", + "tonic", + "tonic-build", ] [[package]] @@ -206,6 +215,51 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa 1.0.15", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -1309,6 +1363,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "endi" version = "1.1.0" @@ -1429,6 +1489,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.1" @@ -1954,6 +2020,25 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "h2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.6.0" @@ -2069,6 +2154,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.6.0" @@ -2078,9 +2169,11 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", + "httpdate", "itoa 1.0.15", "pin-project-lite", "smallvec", @@ -2088,6 +2181,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.11" @@ -2297,6 +2403,15 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -2580,6 +2695,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.7.4" @@ -2670,6 +2791,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "ndk" version = "0.9.0" @@ -3036,6 +3163,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.9.0", +] + [[package]] name = "phf" version = "0.8.0" @@ -3245,6 +3382,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +dependencies = [ + "proc-macro2", + "syn 2.0.100", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -3316,6 +3463,58 @@ dependencies = [ "version_check", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck 0.5.0", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.100", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.40" @@ -4225,6 +4424,17 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.14" @@ -4283,6 +4493,49 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85839f0b32fd242bb3209262371d07feda6d780d16ee9d2bc88581b89da1549b" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85f0383fadd15609306383a90e85eaed44169f931a5d2be1b42c76ceff1825e" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.100", +] + [[package]] name = "tower" version = "0.5.2" @@ -4291,11 +4544,15 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap 2.9.0", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] diff --git a/AobaClient/Cargo.toml b/AobaClient/Cargo.toml index 60066dd..29f6ada 100644 --- a/AobaClient/Cargo.toml +++ b/AobaClient/Cargo.toml @@ -10,6 +10,11 @@ edition = "2021" dioxus = { version = "0.6.0", features = ["router"] } serde = "1.0.219" serde_repr = "0.1.20" +tonic = "*" +prost = "0.13" + +[build-dependencies] +tonic-build = "*" [features] default = ["web"] diff --git a/AobaClient/build.rs b/AobaClient/build.rs new file mode 100644 index 0000000..72d873a --- /dev/null +++ b/AobaClient/build.rs @@ -0,0 +1,7 @@ +fn main() -> Result<(), Box> { + tonic_build::configure() + .build_server(false) + .compile_protos(&["..\\AobaServer\\Proto\\Aoba.proto"], &["..\\AobaServer\\Proto\\"])?; + + Ok(()) +} diff --git a/AobaClient/src/components/basic/button.rs b/AobaClient/src/components/basic/button.rs index 7b3364b..221ad62 100644 --- a/AobaClient/src/components/basic/button.rs +++ b/AobaClient/src/components/basic/button.rs @@ -13,6 +13,7 @@ pub enum ButtonVariant { Accented, } +#[component] pub fn Button(props: ButtonProps) -> Element { rsx! { button { "{props.text}" } diff --git a/AobaClient/src/main.rs b/AobaClient/src/main.rs index 222a6a2..424d125 100644 --- a/AobaClient/src/main.rs +++ b/AobaClient/src/main.rs @@ -2,6 +2,7 @@ pub mod components; mod layouts; pub mod models; pub mod route; +pub mod rpc; pub mod views; use dioxus::prelude::*; diff --git a/AobaClient/src/rpc.rs b/AobaClient/src/rpc.rs new file mode 100644 index 0000000..9b1efb6 --- /dev/null +++ b/AobaClient/src/rpc.rs @@ -0,0 +1,37 @@ +use std::sync::RwLock; + +use aoba::aoba_rpc_client::AobaRpcClient; +use tonic::transport::Channel; + +pub mod aoba { + tonic::include_proto!("aoba"); +} + +static RPC_CLIENT: RpcConnection = RpcConnection { + client: RwLock::new(None), +}; + +#[derive(Default)] +pub struct RpcConnection { + client: RwLock>>, +} + +impl RpcConnection { + pub async fn get_client(&self) -> AobaRpcClient { + self.ensure_client().await; + return self.client.read().unwrap().clone().unwrap(); + } + + async fn ensure_client(&self) { + if self.client.read().unwrap().is_none() { + let c = AobaRpcClient::connect("http://localhost:5000") + .await + .expect("Failed to connect RPC"); + *self.client.write().unwrap() = Some(c); + } + } +} + +pub async fn get_rpc_client() -> AobaRpcClient { + return RPC_CLIENT.get_client().await; +} diff --git a/AobaCore/AobaService.cs b/AobaCore/AobaService.cs index 30417e1..561655e 100644 --- a/AobaCore/AobaService.cs +++ b/AobaCore/AobaService.cs @@ -18,6 +18,18 @@ 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) + { + var filter = string.IsNullOrWhiteSpace(query) ? "{}" : Builders.Filter.Text(query); + var find = _media.Find(filter); + + var total = await find.CountDocumentsAsync(); + page -= 1; + var items = await find.Skip(page * pageSize).Limit(pageSize).ToListAsync(); + return new PagedResult(items, page, pageSize, total); + } + + public Task AddMediaAsync(Media media, CancellationToken cancellationToken = default) { return _media.InsertOneAsync(media, null, cancellationToken); diff --git a/AobaCore/Models/PagedResult.cs b/AobaCore/Models/PagedResult.cs new file mode 100644 index 0000000..98af7e2 --- /dev/null +++ b/AobaCore/Models/PagedResult.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AobaCore.Models; +public class PagedResult(List items, int page, int pageSize, long totalItems) +{ + public List Items { get; set; } = items; + public int Page { get; set; } = page; + public int PageSize { get; set; } = pageSize; + public long TotalItems { get; set; } = totalItems; + public long TotalPages { get; set; } = totalItems / pageSize; + public string? Query { get; set; } +} + + diff --git a/AobaServer/Proto/Aoba.proto b/AobaServer/Proto/Aoba.proto index 91e62ed..4508e59 100644 --- a/AobaServer/Proto/Aoba.proto +++ b/AobaServer/Proto/Aoba.proto @@ -1,23 +1,53 @@ syntax = "proto3"; +option csharp_namespace = "Aoba.RPC"; +package aoba; -service AobaRPC { - rpc GetMedia (Id) returns (MediaModel); +service AobaRpc { + rpc GetMedia (Id) returns (MediaResponse); + rpc ListMedia(PageFilter) returns (ListResponse); +} + +message PageFilter{ + optional int32 page = 1; + optional int32 pageSize = 2; + optional string query = 3; } message Id{ - string idString = 1; + string value = 1; } +message MediaResponse { + oneof result { + MediaModel value = 1; + Empty empty = 2; + } +} + +message ListResponse{ + repeated MediaModel items = 1; + Pagination pagination = 2; +} + +message Pagination { + int32 page = 1; + int32 pageSize = 2; + int64 totalPages = 3; + int64 totalItems = 4; + optional string query = 5; +} + +message Empty{} + message MediaModel { - int32 version = 1; - Id id = 2; - string mediaId = 3; - string fileName = 4; - MediaType mediaType = 5; - string ext = 6; - int32 viewCount = 7; - Id owner = 8; + Id id = 1; + Id mediaId = 2; + string fileName = 3; + MediaType mediaType = 4; + string ext = 5; + int32 viewCount = 6; + Id owner = 7; } enum MediaType{ @@ -27,4 +57,4 @@ enum MediaType{ Text = 3; Code = 4; Raw = 5; -} \ No newline at end of file +} diff --git a/AobaServer/Services/AobaRpcService.cs b/AobaServer/Services/AobaRpcService.cs index c915eff..e448a72 100644 --- a/AobaServer/Services/AobaRpcService.cs +++ b/AobaServer/Services/AobaRpcService.cs @@ -1,13 +1,25 @@ using AobaCore; +using Aoba.RPC; + +using AobaServer.Utils; + using Grpc.Core; namespace AobaServer.Services; -public class AobaRpcService(AobaService aobaService) : AobaRPC.AobaRPCBase +public class AobaRpcService(AobaService aobaService) : AobaRpc.AobaRpcBase { - public override Task GetMedia(Id request, ServerCallContext context) + public override async Task GetMedia(Id request, ServerCallContext context) { - return base.GetMedia(request, context); + var media = await aobaService.GetMediaAsync(request.ToObjectId()); + return media.ToResponse(); } + + 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); + return result.ToResponse(); + } + } diff --git a/AobaServer/Utils/Extensions.cs b/AobaServer/Utils/Extensions.cs new file mode 100644 index 0000000..393d356 --- /dev/null +++ b/AobaServer/Utils/Extensions.cs @@ -0,0 +1,15 @@ +using MongoDB.Bson; + +namespace AobaServer.Utils; + +public static class Extensions +{ + public static ObjectId ToObjectId(this string? value) + { + if(value == null) + return ObjectId.Empty; + if(ObjectId.TryParse(value, out ObjectId result)) + return result; + return ObjectId.Empty; + } +} diff --git a/AobaServer/Utils/ProtoExtensions.cs b/AobaServer/Utils/ProtoExtensions.cs new file mode 100644 index 0000000..ef18910 --- /dev/null +++ b/AobaServer/Utils/ProtoExtensions.cs @@ -0,0 +1,65 @@ +using AobaCore.Models; +using Aoba.RPC; + +using MongoDB.Bson; + +namespace AobaServer.Utils; + +public static class ProtoExtensions +{ + public static ListResponse ToResponse(this PagedResult result) + { + var res = new ListResponse() + { + Pagination = result.ToPagination(), + }; + res.Items.AddRange(result.Items.Select(i => i.ToMediaModel())); + return res; + } + + public static Pagination ToPagination(this PagedResult result) + { + return new Pagination() + { + Page = result.Page, + PageSize = result.PageSize, + TotalItems = result.TotalItems, + TotalPages = result.TotalPages, + Query = result.Query, + }; + } + + public static MediaResponse ToResponse(this Media? media) + { + if(media == null) + return new MediaResponse() { Empty = new Empty() }; + return new MediaResponse() + { + Value = media.ToMediaModel() + }; + } + + public static MediaModel ToMediaModel(this Media media) + { + return new MediaModel() + { + Ext = media.Ext, + FileName = media.Filename, + Id = media.Id.ToId(), + MediaId = media.MediaId.ToId(), + MediaType = (Aoba.RPC.MediaType)media.MediaType, + Owner = media.Owner.ToId(), + ViewCount = media.ViewCount, + }; + } + + public static Id ToId(this ObjectId id) + { + return new Id() { Value = id.ToString() }; + } + + public static ObjectId ToObjectId(this Id id) + { + return id.Value.ToObjectId(); + } +}