configure grpc

This commit is contained in:
2026-01-17 18:08:16 -05:00
parent fc80e50c26
commit b762139243
27 changed files with 467 additions and 500 deletions

30
.dockerignore Normal file
View File

@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@@ -1,21 +0,0 @@
syntax = "proto3";
option csharp_namespace = "AZKi_Server";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}

View File

@@ -1,22 +0,0 @@
using AZKiServer;
using Grpc.Core;
namespace AZKiServer.Services
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}

1
AZKiServer/.dockerignore Normal file
View File

@@ -0,0 +1 @@
wwwroot

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
<Protobuf Include="Protos\*.proto" GrpcServices="Server" />
</ItemGroup>
<ItemGroup>

View File

@@ -12,10 +12,11 @@ public partial class MediaEntry
{
[BsonId]
public ObjectId Id { get; set; }
public int Version { get; set; }
public MediaType Type { get; set; }
public required string Filepath { get; set; }
public DateTime Date { get; set; }
public byte CameraId { get; set; }
public int CameraId { get; set; }
public static Maybe<MediaEntry> Parse(string relativePath)
{
@@ -34,7 +35,7 @@ public partial class MediaEntry
return new MediaEntry
{
CameraId = byte.Parse(cam.Value),
CameraId = int.Parse(cam.Value),
Filepath = relativePath,
Date = ParseDate(date.Value),
Type = ext.Value switch

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
option csharp_namespace = "AZKiServer.RPC";
package azki;
import "google/protobuf/empty.proto";
import "Protos/types.proto";
service AZKi{
rpc GetMediaEntriesInRange(MediaRangeRequest) returns (MediaList);
}

View File

@@ -0,0 +1,33 @@
syntax = "proto3";
option csharp_namespace = "AZKiServer.RPC";
package azki;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
enum MediaType {
None = 0;
Image = 1;
Video = 2;
All = 3;
}
message MediaList {
repeated MediaEntry entries = 1;
}
message MediaRangeRequest{
MediaType type = 1;
google.protobuf.Timestamp from = 2;
google.protobuf.Timestamp to = 3;
}
message MediaEntry {
int32 version = 1;
string id = 2;
MediaType type = 3;
string filePath = 4;
int32 cameraId = 5;
google.protobuf.Timestamp date = 6;
}

View File

@@ -0,0 +1,41 @@
namespace AZKiServer.RPC;
public static class ConversionExtensions
{
public static Models.MediaType FromRpc(this MediaType type)
{
return type switch
{
MediaType.None => Models.MediaType.None,
MediaType.Image => Models.MediaType.Image,
MediaType.Video => Models.MediaType.Video,
MediaType.All => Models.MediaType.All,
_ => throw new NotSupportedException()
};
}
public static MediaType ToRpc(this Models.MediaType type)
{
return type switch
{
Models.MediaType.None => MediaType.None,
Models.MediaType.Image => MediaType.Image,
Models.MediaType.Video => MediaType.Video,
Models.MediaType.All => MediaType.All,
_ => throw new NotSupportedException()
};
}
public static MediaEntry ToRpc(this Models.MediaEntry entry)
{
return new MediaEntry
{
Version = entry.Version,
CameraId = entry.CameraId,
Date = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(entry.Date),
FilePath = entry.Filepath,
Id = entry.Id.ToString(),
Type = entry.Type.ToRpc()
};
}
}

View File

@@ -0,0 +1,20 @@
using AZKiServer.Models;
using AZKiServer.RPC;
using Google.Protobuf;
using Grpc.Core;
namespace AZKiServer.Services;
public class AZKiRpcService(MediaService mediaService) : RPC.AZKi.AZKiBase
{
public override async Task<MediaList> GetMediaEntriesInRange(MediaRangeRequest request, ServerCallContext context)
{
var from = request.From.ToDateTime();
var to = request.To.ToDateTime();
var items = await mediaService.GetEntriesInRangeAsync(request.Type.FromRpc(), from, to);
var result = new MediaList();
result.Entries.AddRange(items.Select(e => e.ToRpc()));
return result;
}
}

65
Dockerfile Normal file
View File

@@ -0,0 +1,65 @@
# Client Side build - prep deps
FROM rust:1-trixie AS chef
RUN rustup target add wasm32-unknown-unknown
RUN cargo install cargo-chef
WORKDIR /app
FROM chef AS planner
COPY . .
WORKDIR /app/client
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS client-builder
WORKDIR /app/client
COPY --from=planner /app/client/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
COPY /client /app/client
COPY /AZKiServer/Protos /app/AZKiServer/Protos
# Install Protobuf
RUN apt update
RUN apt install -y protobuf-compiler libprotobuf-dev ffmpeg
# Install `dx`
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
RUN cargo binstall dioxus-cli --root /.cargo -y --force
ENV PATH="/.cargo/bin:$PATH"
ARG VERSION
ENV APP_VERSION=$VERSION
# Create the final bundle folder. Bundle always executes in release mode with optimizations enabled
RUN dx bundle --release --platform web
# Server Build
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
RUN apt-get update && apt-get install -y ffmpeg #libvips libvips-tools
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0-noble AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["AZKiServer/AZKi Server.csproj", "AZKiServer/"]
RUN dotnet restore "./AZKiServer/AZKi Server.csproj"
COPY . .
# Copy Built bundle from client builder
COPY --from=client-builder /app/client/target/dx/azki-client/release/web/public /src/AZKiServer/wwwroot
WORKDIR "/src/AZKiServer"
# RUN dotnet build "./AZKiServer.csproj" -c $BUILD_CONFIGURATION #-o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./AZKi Server.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ARG VERSION
ENV APP_VERSION=$VERSION
ENTRYPOINT ["dotnet", "AZKiServer.dll"]

View File

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36908.2 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AZKi Server", "AZKi Server\AZKi Server.csproj", "{15810AAC-9BCB-42AD-90B3-F1AA8E134F82}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AZKi Server", "AZKiServer\AZKi Server.csproj", "{15810AAC-9BCB-42AD-90B3-F1AA8E134F82}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

6
client/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
**/target
**/dist
LICENSES
LICENSE
temp
README.md

619
client/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[package]
name = "azki"
name = "azki-client"
version = "0.1.0"
authors = ["Amatsugu <khamraj@kaisei.app>"]
edition = "2021"
@@ -7,19 +7,21 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus = { version = "0.7.1", features = ["router"] }
dioxus = { version = "0.7.3", features = ["router"] }
serde = "1.0.219"
serde_repr = "0.1.20"
tonic = { version = "*", default-features = false, features = [
tonic = { version = "0.14.2", default-features = false, features = [
"codegen",
"prost",
] }
prost = "0.13"
tonic-web-wasm-client = "0.7"
prost = "0.14"
prost-types = "0.14"
tonic-web-wasm-client = "0.8"
web-sys = { version = "0.3.77", features = ["Storage", "Window"] }
tokio = "1.49.0"
tonic-prost = "0.14.2"
[build-dependencies]
tonic-build = { version = "*", default-features = false, features = ["prost"] }
tonic-prost-build = { version = "0.14.2"}
dotenv = "0.15.0"
[features]

View File

@@ -4,15 +4,14 @@ use std::fs::File;
use std::io::Write;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// tonic_build::configure()
// .build_server(false)
// .build_client(true)
// .compile_protos(
// &[
// "",
// ],
// &[""],
// )?;
tonic_prost_build::configure()
.build_server(false)
.build_client(true)
.build_transport(false)
.compile_protos(
&["../AZKiServer/Protos/azki.proto", "../AZKiServer/Protos/types.proto"],
&["../AZKiServer"],
)?;
forward_env();
Ok(())
}

View File

@@ -4,8 +4,14 @@ mod app;
mod components;
mod env;
mod route;
mod rpc;
mod views;
#[cfg(debug_assertions)]
pub const RPC_HOST: &'static str = "http://localhost:8081";
#[cfg(not(debug_assertions))]
pub const RPC_HOST: &'static str = "https://grpc.aoba.app:8443";
fn main() {
dioxus::launch(App);
}

49
client/src/rpc.rs Normal file
View File

@@ -0,0 +1,49 @@
use std::sync::RwLock;
use tonic::service::{interceptor::InterceptedService, Interceptor};
use tonic_web_wasm_client::Client;
use crate::{rpc::azki::az_ki_client::AzKiClient, RPC_HOST};
pub mod azki {
tonic::include_proto!("azki");
}
static RPC_CLIENT: RpcConnection = RpcConnection {
azki: RwLock::new(None),
jwt: RwLock::new(None),
};
#[derive(Default)]
pub struct RpcConnection {
azki: RwLock<Option<AzKiClient<InterceptedService<Client, AuthInterceptor>>>>,
jwt: RwLock<Option<String>>,
}
impl RpcConnection {
pub fn get_client(&self) -> AzKiClient<InterceptedService<Client, AuthInterceptor>> {
self.ensure_client();
return self.azki.read().unwrap().clone().unwrap();
}
fn ensure_client(&self) {
if self.azki.read().unwrap().is_none() {
let wasm_client = Client::new(RPC_HOST.into());
let aoba_client = AzKiClient::with_interceptor(wasm_client.clone(), AuthInterceptor);
*self.azki.write().unwrap() = Some(aoba_client);
}
}
}
#[derive(Clone)]
pub struct AuthInterceptor;
impl Interceptor for AuthInterceptor {
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, 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);
}
}