feat: server can actually create root and sections
All checks were successful
continuous-integration/drone/push Build is passing

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-05-13 22:57:20 +02:00
parent 76f1c87663
commit 699bac7159
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
12 changed files with 276 additions and 49 deletions

View File

@ -32,11 +32,29 @@ message GraphItem {
} }
service Graph { service Graph {
// Commands
rpc CreateSection(CreateSectionRequest) returns (CreateSectionResponse);
rpc CreateRoot(CreateRootRequest) returns (CreateRootResponse);
// Queriers
rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse); rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse);
rpc Get(GetRequest) returns (GetReply); rpc Get(GetRequest) returns (GetReply);
rpc CreateSection(CreateSectionRequest) returns (CreateSectionResponse);
} }
// Commands
message CreateSectionRequest {
string root = 1;
repeated string path = 2;
}
message CreateSectionResponse {}
message CreateRootRequest {
string root = 1;
}
message CreateRootResponse {}
// Queries
message GetAvailableRootsRequest {} message GetAvailableRootsRequest {}
message GetAvailableRootsResponse { message GetAvailableRootsResponse {
repeated string roots = 1; repeated string roots = 1;
@ -50,8 +68,3 @@ message GetRequest {
message GetReply { message GetReply {
GraphItem item = 1; GraphItem item = 1;
} }
message CreateSectionRequest {}
message CreateSectionResponse {}

View File

@ -1 +1,14 @@
-- Add migration script here -- Add migration script here
CREATE TABLE roots (
id UUID NOT NULL PRIMARY KEY,
root_name VARCHAR(255) UNIQUE NOT NULL
);
CREATE TABLE nodes (
id UUID NOT NULL PRIMARY KEY,
root_id UUID NOT NULL,
path VARCHAR NOT NULL,
item_type VARCHAR NOT NULL,
item_content JSONB
);

View File

@ -1,5 +1,13 @@
use hyperlog_core::log::{GraphItem, ItemState}; use hyperlog_core::log::{GraphItem, ItemState};
use crate::{
services::{
create_root::{self, CreateRoot, CreateRootExt},
create_section::{self, CreateSection, CreateSectionExt},
},
state::SharedState,
};
#[allow(dead_code)] #[allow(dead_code)]
pub enum Command { pub enum Command {
CreateRoot { CreateRoot {
@ -35,14 +43,35 @@ pub enum Command {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub struct Commander {} pub struct Commander {
create_root: CreateRoot,
create_section: CreateSection,
}
#[allow(dead_code, unused_variables)]
impl Commander { impl Commander {
pub fn execute(&self, cmd: Command) -> anyhow::Result<()> { pub fn new(create_root: CreateRoot, create_section: CreateSection) -> Self {
Self {
create_root,
create_section,
}
}
pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> {
match cmd { match cmd {
Command::CreateRoot { root } => todo!(), Command::CreateRoot { root } => {
Command::CreateSection { root, path } => todo!(), self.create_root
.execute(create_root::Request { root })
.await?;
Ok(())
}
Command::CreateSection { root, path } => {
self.create_section
.execute(create_section::Request { root, path })
.await?;
Ok(())
}
Command::CreateItem { Command::CreateItem {
root, root,
path, path,
@ -61,38 +90,14 @@ impl Commander {
Command::Move { root, src, dest } => todo!(), Command::Move { root, src, dest } => todo!(),
} }
} }
}
pub async fn create_root(&self, root: &str) -> anyhow::Result<()> { pub trait CommanderExt {
todo!() fn commander(&self) -> Commander;
} }
pub async fn create(&self, root: &str, path: &[&str], item: GraphItem) -> anyhow::Result<()> { impl CommanderExt for SharedState {
todo!() fn commander(&self) -> Commander {
} Commander::new(self.create_root_service(), self.create_section_service())
pub async fn get(&self, root: &str, path: &[&str]) -> Option<GraphItem> {
todo!()
}
pub async fn section_move(
&self,
root: &str,
src_path: &[&str],
dest_path: &[&str],
) -> anyhow::Result<()> {
todo!()
}
pub async fn delete(&self, root: &str, path: &[&str]) -> anyhow::Result<()> {
todo!()
}
pub async fn update_item(
&self,
root: &str,
path: &[&str],
item: &GraphItem,
) -> anyhow::Result<()> {
todo!()
} }
} }

View File

@ -6,6 +6,7 @@ use std::{collections::HashMap, net::SocketAddr};
use tonic::{transport, Response}; use tonic::{transport, Response};
use crate::{ use crate::{
commands::{Command, Commander, CommanderExt},
querier::{Querier, QuerierExt}, querier::{Querier, QuerierExt},
state::SharedState, state::SharedState,
}; };
@ -13,11 +14,12 @@ use crate::{
#[allow(dead_code)] #[allow(dead_code)]
pub struct Server { pub struct Server {
querier: Querier, querier: Querier,
commander: Commander,
} }
impl Server { impl Server {
pub fn new(querier: Querier) -> Self { pub fn new(querier: Querier, commander: Commander) -> Self {
Self { querier } Self { querier, commander }
} }
} }
@ -72,8 +74,85 @@ impl Graph for Server {
let req = request.into_inner(); let req = request.into_inner();
tracing::trace!("create section: req({:?})", req); tracing::trace!("create section: req({:?})", req);
if req.root.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"root cannot be empty".to_string(),
));
}
if req.path.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot be empty".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.is_empty())
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain empty paths".to_string(),
));
}
if req
.path
.iter()
.filter(|item| item.contains("."))
.collect::<Vec<_>>()
.first()
.is_some()
{
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"path cannot contain `.`".to_string(),
));
}
self.commander
.execute(Command::CreateSection {
root: req.root,
path: req.path,
})
.await
.map_err(to_tonic_err)?;
Ok(Response::new(CreateSectionResponse {})) Ok(Response::new(CreateSectionResponse {}))
} }
async fn create_root(
&self,
request: tonic::Request<CreateRootRequest>,
) -> std::result::Result<tonic::Response<CreateRootResponse>, tonic::Status> {
let req = request.into_inner();
tracing::trace!("create root: req({:?})", req);
if req.root.is_empty() {
return Err(tonic::Status::new(
tonic::Code::InvalidArgument,
"root cannot be empty".to_string(),
));
}
self.commander
.execute(Command::CreateRoot { root: req.root })
.await
.map_err(to_tonic_err)?;
Ok(Response::new(CreateRootResponse {}))
}
}
// TODO: create more defined protobuf categories for errors
fn to_tonic_err(err: anyhow::Error) -> tonic::Status {
tonic::Status::new(tonic::Code::Unknown, err.to_string())
} }
pub trait ServerExt { pub trait ServerExt {
@ -82,7 +161,7 @@ pub trait ServerExt {
impl ServerExt for SharedState { impl ServerExt for SharedState {
fn grpc_server(&self) -> Server { fn grpc_server(&self) -> Server {
Server::new(self.querier()) Server::new(self.querier(), self.commander())
} }
} }

View File

@ -11,6 +11,8 @@ mod querier;
mod state; mod state;
mod services;
#[derive(Clone)] #[derive(Clone)]
pub struct ServeOptions { pub struct ServeOptions {
pub external_http: SocketAddr, pub external_http: SocketAddr,

View File

@ -0,0 +1,2 @@
pub mod create_root;
pub mod create_section;

View File

@ -0,0 +1,38 @@
use crate::state::SharedState;
#[derive(Clone)]
pub struct CreateRoot {
db: sqlx::PgPool,
}
pub struct Request {
pub root: String,
}
pub struct Response {}
impl CreateRoot {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
let root_id = uuid::Uuid::new_v4();
sqlx::query(r#"INSERT INTO roots (id, root_name) VALUES ($1, $2)"#)
.bind(root_id)
.bind(req.root)
.execute(&self.db)
.await?;
Ok(Response {})
}
}
pub trait CreateRootExt {
fn create_root_service(&self) -> CreateRoot;
}
impl CreateRootExt for SharedState {
fn create_root_service(&self) -> CreateRoot {
CreateRoot::new(self.db.clone())
}
}

View File

@ -0,0 +1,62 @@
use hyperlog_core::log::GraphItem;
use crate::state::SharedState;
#[derive(Clone)]
pub struct CreateSection {
db: sqlx::PgPool,
}
pub struct Request {
pub root: String,
pub path: Vec<String>,
}
pub struct Response {}
#[derive(sqlx::FromRow)]
struct Root {
id: uuid::Uuid,
root_name: String,
}
impl CreateSection {
pub fn new(db: sqlx::PgPool) -> Self {
Self { db }
}
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
let Root { id: root_id, .. } =
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
.bind(req.root)
.fetch_one(&self.db)
.await?;
let node_id = uuid::Uuid::new_v4();
sqlx::query(
r#"
INSERT INTO nodes
(id, root_id, path, item_type, item_content)
VALUES
($1, $2, $3, $4, $5)"#,
)
.bind(node_id)
.bind(root_id)
.bind(req.path.join("."))
.bind("SECTION".to_string())
.bind(None::<serde_json::Value>)
.execute(&self.db)
.await?;
Ok(Response {})
}
}
pub trait CreateSectionExt {
fn create_section_service(&self) -> CreateSection;
}
impl CreateSectionExt for SharedState {
fn create_section_service(&self) -> CreateSection {
CreateSection::new(self.db.clone())
}
}

View File

@ -15,7 +15,7 @@ impl Deref for SharedState {
} }
pub struct State { pub struct State {
pub _db: Pool<Postgres>, pub db: Pool<Postgres>,
} }
impl State { impl State {
@ -32,6 +32,6 @@ impl State {
let _ = sqlx::query("SELECT 1;").fetch_one(&db).await?; let _ = sqlx::query("SELECT 1;").fetch_one(&db).await?;
Ok(Self { _db: db }) Ok(Self { db })
} }
} }

View File

@ -20,7 +20,13 @@ impl Commander {
match cmd.clone() { match cmd.clone() {
Command::CreateRoot { root } => { Command::CreateRoot { root } => {
todo!() let channel = self.channel.clone();
let mut client = GraphClient::new(channel);
let request = tonic::Request::new(CreateRootRequest { root });
let response = client.create_root(request).await?;
let res = response.into_inner();
//self.engine.create_root(&root)?; //self.engine.create_root(&root)?;
} }
Command::CreateSection { root, path } => { Command::CreateSection { root, path } => {
@ -28,7 +34,7 @@ impl Commander {
let mut client = GraphClient::new(channel); let mut client = GraphClient::new(channel);
let request = tonic::Request::new(CreateSectionRequest {}); let request = tonic::Request::new(CreateSectionRequest { root, path });
let response = client.create_section(request).await?; let response = client.create_section(request).await?;
let res = response.into_inner(); let res = response.into_inner();

View File

@ -19,3 +19,5 @@ please:
scripts: scripts:
dev: dev:
type: shell type: shell
install:
type: shell

5
scripts/install.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env zsh
set -eo pipefail
cargo install --path crates/hyperlog --force