Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
699bac7159
commit
7bdf8393b1
@ -35,6 +35,7 @@ service Graph {
|
|||||||
// Commands
|
// Commands
|
||||||
rpc CreateSection(CreateSectionRequest) returns (CreateSectionResponse);
|
rpc CreateSection(CreateSectionRequest) returns (CreateSectionResponse);
|
||||||
rpc CreateRoot(CreateRootRequest) returns (CreateRootResponse);
|
rpc CreateRoot(CreateRootRequest) returns (CreateRootResponse);
|
||||||
|
rpc CreateItem(CreateItemRequest) returns (CreateItemResponse);
|
||||||
|
|
||||||
// Queriers
|
// Queriers
|
||||||
rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse);
|
rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse);
|
||||||
@ -54,6 +55,13 @@ message CreateRootRequest {
|
|||||||
}
|
}
|
||||||
message CreateRootResponse {}
|
message CreateRootResponse {}
|
||||||
|
|
||||||
|
message CreateItemRequest {
|
||||||
|
string root = 1;
|
||||||
|
repeated string path = 2;
|
||||||
|
ItemGraphItem item = 3;
|
||||||
|
}
|
||||||
|
message CreateItemResponse {}
|
||||||
|
|
||||||
// Queries
|
// Queries
|
||||||
message GetAvailableRootsRequest {}
|
message GetAvailableRootsRequest {}
|
||||||
message GetAvailableRootsResponse {
|
message GetAvailableRootsResponse {
|
||||||
|
@ -12,3 +12,6 @@ CREATE TABLE nodes (
|
|||||||
item_type VARCHAR NOT NULL,
|
item_type VARCHAR NOT NULL,
|
||||||
item_content JSONB
|
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::{
|
use crate::{
|
||||||
services::{
|
services::{
|
||||||
|
create_item::{self, CreateItem, CreateItemExt},
|
||||||
create_root::{self, CreateRoot, CreateRootExt},
|
create_root::{self, CreateRoot, CreateRootExt},
|
||||||
create_section::{self, CreateSection, CreateSectionExt},
|
create_section::{self, CreateSection, CreateSectionExt},
|
||||||
},
|
},
|
||||||
@ -46,13 +47,19 @@ pub enum Command {
|
|||||||
pub struct Commander {
|
pub struct Commander {
|
||||||
create_root: CreateRoot,
|
create_root: CreateRoot,
|
||||||
create_section: CreateSection,
|
create_section: CreateSection,
|
||||||
|
create_item: CreateItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Commander {
|
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 {
|
Self {
|
||||||
create_root,
|
create_root,
|
||||||
create_section,
|
create_section,
|
||||||
|
create_item,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +85,19 @@ impl Commander {
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
state,
|
state,
|
||||||
} => todo!(),
|
} => {
|
||||||
|
self.create_item
|
||||||
|
.execute(create_item::Request {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Command::UpdateItem {
|
Command::UpdateItem {
|
||||||
root,
|
root,
|
||||||
path,
|
path,
|
||||||
@ -98,6 +117,10 @@ pub trait CommanderExt {
|
|||||||
|
|
||||||
impl CommanderExt for SharedState {
|
impl CommanderExt for SharedState {
|
||||||
fn commander(&self) -> Commander {
|
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]
|
#[tonic::async_trait]
|
||||||
impl Graph for Server {
|
impl Graph for Server {
|
||||||
async fn get(
|
async fn create_item(
|
||||||
&self,
|
&self,
|
||||||
request: tonic::Request<GetRequest>,
|
request: tonic::Request<CreateItemRequest>,
|
||||||
) -> std::result::Result<tonic::Response<GetReply>, tonic::Status> {
|
) -> std::result::Result<tonic::Response<CreateItemResponse>, tonic::Status> {
|
||||||
let msg = request.get_ref();
|
let req = request.into_inner();
|
||||||
|
tracing::trace!("create item: req({:?})", req);
|
||||||
|
|
||||||
tracing::trace!("get: req({:?})", msg);
|
if req.root.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
Ok(Response::new(GetReply {
|
tonic::Code::InvalidArgument,
|
||||||
item: Some(GraphItem {
|
"root cannot be empty".to_string(),
|
||||||
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 {},
|
|
||||||
)),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
)]),
|
|
||||||
})),
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_available_roots(
|
if req.path.is_empty() {
|
||||||
&self,
|
return Err(tonic::Status::new(
|
||||||
request: tonic::Request<GetAvailableRootsRequest>,
|
tonic::Code::InvalidArgument,
|
||||||
) -> std::result::Result<tonic::Response<GetAvailableRootsResponse>, tonic::Status> {
|
"path cannot be empty".to_string(),
|
||||||
let req = request.into_inner();
|
));
|
||||||
tracing::trace!("get available roots: req({:?})", req);
|
}
|
||||||
|
|
||||||
Ok(Response::new(GetAvailableRootsResponse {
|
if req
|
||||||
roots: vec!["kjuulh".into()],
|
.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(
|
async fn create_section(
|
||||||
@ -127,26 +188,46 @@ impl Graph for Server {
|
|||||||
Ok(Response::new(CreateSectionResponse {}))
|
Ok(Response::new(CreateSectionResponse {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_root(
|
async fn get(
|
||||||
&self,
|
&self,
|
||||||
request: tonic::Request<CreateRootRequest>,
|
request: tonic::Request<GetRequest>,
|
||||||
) -> std::result::Result<tonic::Response<CreateRootResponse>, tonic::Status> {
|
) -> std::result::Result<tonic::Response<GetReply>, tonic::Status> {
|
||||||
let req = request.into_inner();
|
let msg = request.get_ref();
|
||||||
tracing::trace!("create root: req({:?})", req);
|
|
||||||
|
|
||||||
if req.root.is_empty() {
|
tracing::trace!("get: req({:?})", msg);
|
||||||
return Err(tonic::Status::new(
|
|
||||||
tonic::Code::InvalidArgument,
|
Ok(Response::new(GetReply {
|
||||||
"root cannot be empty".to_string(),
|
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
|
async fn get_available_roots(
|
||||||
.execute(Command::CreateRoot { root: req.root })
|
&self,
|
||||||
.await
|
request: tonic::Request<GetAvailableRootsRequest>,
|
||||||
.map_err(to_tonic_err)?;
|
) -> 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_root;
|
||||||
pub mod create_section;
|
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)
|
.fetch_one(&self.db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// FIXME: implement consistency check on path
|
||||||
|
|
||||||
let node_id = uuid::Uuid::new_v4();
|
let node_id = uuid::Uuid::new_v4();
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
|
@ -51,7 +51,28 @@ impl Commander {
|
|||||||
description,
|
description,
|
||||||
state,
|
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(
|
// self.engine.create(
|
||||||
// &root,
|
// &root,
|
||||||
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||||
|
Loading…
Reference in New Issue
Block a user