Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
699bac7159
commit
7bdf8393b1
@ -35,6 +35,7 @@ service Graph {
|
||||
// Commands
|
||||
rpc CreateSection(CreateSectionRequest) returns (CreateSectionResponse);
|
||||
rpc CreateRoot(CreateRootRequest) returns (CreateRootResponse);
|
||||
rpc CreateItem(CreateItemRequest) returns (CreateItemResponse);
|
||||
|
||||
// Queriers
|
||||
rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse);
|
||||
@ -54,6 +55,13 @@ message CreateRootRequest {
|
||||
}
|
||||
message CreateRootResponse {}
|
||||
|
||||
message CreateItemRequest {
|
||||
string root = 1;
|
||||
repeated string path = 2;
|
||||
ItemGraphItem item = 3;
|
||||
}
|
||||
message CreateItemResponse {}
|
||||
|
||||
// Queries
|
||||
message GetAvailableRootsRequest {}
|
||||
message GetAvailableRootsResponse {
|
||||
|
@ -12,3 +12,6 @@ CREATE TABLE nodes (
|
||||
item_type VARCHAR NOT NULL,
|
||||
item_content JSONB
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_unique_root_path ON nodes(root_id, path);
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use hyperlog_core::log::{GraphItem, ItemState};
|
||||
use hyperlog_core::log::ItemState;
|
||||
|
||||
use crate::{
|
||||
services::{
|
||||
create_item::{self, CreateItem, CreateItemExt},
|
||||
create_root::{self, CreateRoot, CreateRootExt},
|
||||
create_section::{self, CreateSection, CreateSectionExt},
|
||||
},
|
||||
@ -46,13 +47,19 @@ pub enum Command {
|
||||
pub struct Commander {
|
||||
create_root: CreateRoot,
|
||||
create_section: CreateSection,
|
||||
create_item: CreateItem,
|
||||
}
|
||||
|
||||
impl Commander {
|
||||
pub fn new(create_root: CreateRoot, create_section: CreateSection) -> Self {
|
||||
pub fn new(
|
||||
create_root: CreateRoot,
|
||||
create_section: CreateSection,
|
||||
create_item: CreateItem,
|
||||
) -> Self {
|
||||
Self {
|
||||
create_root,
|
||||
create_section,
|
||||
create_item,
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +85,19 @@ impl Commander {
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
} => todo!(),
|
||||
} => {
|
||||
self.create_item
|
||||
.execute(create_item::Request {
|
||||
root,
|
||||
path,
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Command::UpdateItem {
|
||||
root,
|
||||
path,
|
||||
@ -98,6 +117,10 @@ pub trait CommanderExt {
|
||||
|
||||
impl CommanderExt for SharedState {
|
||||
fn commander(&self) -> Commander {
|
||||
Commander::new(self.create_root_service(), self.create_section_service())
|
||||
Commander::new(
|
||||
self.create_root_service(),
|
||||
self.create_section_service(),
|
||||
self.create_item_service(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,46 +25,107 @@ impl Server {
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Graph for Server {
|
||||
async fn get(
|
||||
async fn create_item(
|
||||
&self,
|
||||
request: tonic::Request<GetRequest>,
|
||||
) -> std::result::Result<tonic::Response<GetReply>, tonic::Status> {
|
||||
let msg = request.get_ref();
|
||||
request: tonic::Request<CreateItemRequest>,
|
||||
) -> std::result::Result<tonic::Response<CreateItemResponse>, tonic::Status> {
|
||||
let req = request.into_inner();
|
||||
tracing::trace!("create item: req({:?})", req);
|
||||
|
||||
tracing::trace!("get: req({:?})", msg);
|
||||
|
||||
Ok(Response::new(GetReply {
|
||||
item: Some(GraphItem {
|
||||
path: "kjuulh".into(),
|
||||
contents: Some(graph_item::Contents::User(UserGraphItem {
|
||||
items: HashMap::from([(
|
||||
"some".to_string(),
|
||||
GraphItem {
|
||||
path: "some".into(),
|
||||
contents: Some(graph_item::Contents::Item(ItemGraphItem {
|
||||
title: "some-title".into(),
|
||||
description: "some-description".into(),
|
||||
item_state: Some(item_graph_item::ItemState::NotDone(
|
||||
ItemStateNotDone {},
|
||||
)),
|
||||
})),
|
||||
},
|
||||
)]),
|
||||
})),
|
||||
}),
|
||||
}))
|
||||
if req.root.is_empty() {
|
||||
return Err(tonic::Status::new(
|
||||
tonic::Code::InvalidArgument,
|
||||
"root cannot be empty".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
async fn get_available_roots(
|
||||
&self,
|
||||
request: tonic::Request<GetAvailableRootsRequest>,
|
||||
) -> std::result::Result<tonic::Response<GetAvailableRootsResponse>, tonic::Status> {
|
||||
let req = request.into_inner();
|
||||
tracing::trace!("get available roots: req({:?})", req);
|
||||
if req.path.is_empty() {
|
||||
return Err(tonic::Status::new(
|
||||
tonic::Code::InvalidArgument,
|
||||
"path cannot be empty".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Response::new(GetAvailableRootsResponse {
|
||||
roots: vec!["kjuulh".into()],
|
||||
}))
|
||||
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(),
|
||||
));
|
||||
}
|
||||
|
||||
let item = match req.item {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
return Err(tonic::Status::new(
|
||||
tonic::Code::InvalidArgument,
|
||||
"item cannot contain empty or null".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
self.commander
|
||||
.execute(Command::CreateItem {
|
||||
root: req.root,
|
||||
path: req.path,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
state: match item.item_state {
|
||||
Some(item_graph_item::ItemState::Done(_)) => {
|
||||
hyperlog_core::log::ItemState::Done
|
||||
}
|
||||
Some(item_graph_item::ItemState::NotDone(_)) => {
|
||||
hyperlog_core::log::ItemState::NotDone
|
||||
}
|
||||
None => hyperlog_core::log::ItemState::default(),
|
||||
},
|
||||
})
|
||||
.await
|
||||
.map_err(to_tonic_err)?;
|
||||
|
||||
Ok(Response::new(CreateItemResponse {}))
|
||||
}
|
||||
|
||||
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 {}))
|
||||
}
|
||||
|
||||
async fn create_section(
|
||||
@ -127,26 +188,46 @@ impl Graph for Server {
|
||||
Ok(Response::new(CreateSectionResponse {}))
|
||||
}
|
||||
|
||||
async fn create_root(
|
||||
async fn get(
|
||||
&self,
|
||||
request: tonic::Request<CreateRootRequest>,
|
||||
) -> std::result::Result<tonic::Response<CreateRootResponse>, tonic::Status> {
|
||||
let req = request.into_inner();
|
||||
tracing::trace!("create root: req({:?})", req);
|
||||
request: tonic::Request<GetRequest>,
|
||||
) -> std::result::Result<tonic::Response<GetReply>, tonic::Status> {
|
||||
let msg = request.get_ref();
|
||||
|
||||
if req.root.is_empty() {
|
||||
return Err(tonic::Status::new(
|
||||
tonic::Code::InvalidArgument,
|
||||
"root cannot be empty".to_string(),
|
||||
));
|
||||
tracing::trace!("get: req({:?})", msg);
|
||||
|
||||
Ok(Response::new(GetReply {
|
||||
item: Some(GraphItem {
|
||||
path: "kjuulh".into(),
|
||||
contents: Some(graph_item::Contents::User(UserGraphItem {
|
||||
items: HashMap::from([(
|
||||
"some".to_string(),
|
||||
GraphItem {
|
||||
path: "some".into(),
|
||||
contents: Some(graph_item::Contents::Item(ItemGraphItem {
|
||||
title: "some-title".into(),
|
||||
description: "some-description".into(),
|
||||
item_state: Some(item_graph_item::ItemState::NotDone(
|
||||
ItemStateNotDone {},
|
||||
)),
|
||||
})),
|
||||
},
|
||||
)]),
|
||||
})),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
self.commander
|
||||
.execute(Command::CreateRoot { root: req.root })
|
||||
.await
|
||||
.map_err(to_tonic_err)?;
|
||||
async fn get_available_roots(
|
||||
&self,
|
||||
request: tonic::Request<GetAvailableRootsRequest>,
|
||||
) -> std::result::Result<tonic::Response<GetAvailableRootsResponse>, tonic::Status> {
|
||||
let req = request.into_inner();
|
||||
tracing::trace!("get available roots: req({:?})", req);
|
||||
|
||||
Ok(Response::new(CreateRootResponse {}))
|
||||
Ok(Response::new(GetAvailableRootsResponse {
|
||||
roots: vec!["kjuulh".into()],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod create_item;
|
||||
pub mod create_root;
|
||||
pub mod create_section;
|
||||
|
107
crates/hyperlog-server/src/services/create_item.rs
Normal file
107
crates/hyperlog-server/src/services/create_item.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use hyperlog_core::log::{GraphItem, ItemState};
|
||||
use sqlx::types::Json;
|
||||
|
||||
use crate::state::SharedState;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CreateItem {
|
||||
db: sqlx::PgPool,
|
||||
}
|
||||
|
||||
pub struct Request {
|
||||
pub root: String,
|
||||
pub path: Vec<String>,
|
||||
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub state: ItemState,
|
||||
}
|
||||
pub struct Response {}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct ItemContent {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub state: ItemState,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Root {
|
||||
id: uuid::Uuid,
|
||||
root_name: String,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Section {
|
||||
id: uuid::Uuid,
|
||||
}
|
||||
|
||||
impl CreateItem {
|
||||
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?;
|
||||
|
||||
match req.path.split_last() {
|
||||
Some((_, section_path)) => {
|
||||
if !section_path.is_empty() {
|
||||
let Section { .. } = sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
nodes
|
||||
WHERE
|
||||
root_id = $1 AND
|
||||
path = $2 AND
|
||||
item_type = 'SECTION'
|
||||
"#,
|
||||
)
|
||||
.bind(root_id)
|
||||
.bind(section_path.join("."))
|
||||
.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("ITEM".to_string())
|
||||
.bind(Json(ItemContent {
|
||||
title: req.title,
|
||||
description: req.description,
|
||||
state: req.state,
|
||||
}))
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
}
|
||||
None => anyhow::bail!("path most contain at least one item"),
|
||||
}
|
||||
|
||||
Ok(Response {})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CreateItemExt {
|
||||
fn create_item_service(&self) -> CreateItem;
|
||||
}
|
||||
|
||||
impl CreateItemExt for SharedState {
|
||||
fn create_item_service(&self) -> CreateItem {
|
||||
CreateItem::new(self.db.clone())
|
||||
}
|
||||
}
|
@ -31,6 +31,8 @@ impl CreateSection {
|
||||
.fetch_one(&self.db)
|
||||
.await?;
|
||||
|
||||
// FIXME: implement consistency check on path
|
||||
|
||||
let node_id = uuid::Uuid::new_v4();
|
||||
sqlx::query(
|
||||
r#"
|
||||
|
@ -51,7 +51,28 @@ impl Commander {
|
||||
description,
|
||||
state,
|
||||
} => {
|
||||
todo!()
|
||||
let channel = self.channel.clone();
|
||||
|
||||
let mut client = GraphClient::new(channel);
|
||||
|
||||
let request = tonic::Request::new(CreateItemRequest {
|
||||
root,
|
||||
path,
|
||||
item: Some(ItemGraphItem {
|
||||
title,
|
||||
description,
|
||||
item_state: Some(match state {
|
||||
hyperlog_core::log::ItemState::NotDone => {
|
||||
item_graph_item::ItemState::NotDone(ItemStateNotDone {})
|
||||
}
|
||||
hyperlog_core::log::ItemState::Done => {
|
||||
item_graph_item::ItemState::Done(ItemStateDone {})
|
||||
}
|
||||
}),
|
||||
}),
|
||||
});
|
||||
let response = client.create_item(request).await?;
|
||||
let res = response.into_inner();
|
||||
// self.engine.create(
|
||||
// &root,
|
||||
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
|
Loading…
Reference in New Issue
Block a user