From 9587c60e728c8fe45fc52451eb072ef1437e3ce2 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sat, 1 Jun 2024 13:13:18 +0200 Subject: [PATCH] feat: add archive sub command Signed-off-by: kjuulh --- crates/hyperlog-protos/proto/hyperlog.proto | 7 ++ ...0601104242_add_archive_column_to_nodes.sql | 3 + crates/hyperlog-server/src/commands.rs | 16 +++++ crates/hyperlog-server/src/external_grpc.rs | 32 +++++++++ crates/hyperlog-server/src/services.rs | 1 + .../hyperlog-server/src/services/archive.rs | 70 +++++++++++++++++++ .../hyperlog-server/src/services/get_graph.rs | 1 + crates/hyperlog-tui/src/app.rs | 3 +- crates/hyperlog-tui/src/commander.rs | 4 ++ crates/hyperlog-tui/src/commander/local.rs | 3 + crates/hyperlog-tui/src/commander/remote.rs | 42 +++-------- crates/hyperlog-tui/src/commands.rs | 1 + crates/hyperlog-tui/src/commands/archive.rs | 58 +++++++++++++++ .../src/components/graph_explorer.rs | 23 +++--- crates/hyperlog-tui/src/engine.rs | 6 ++ crates/hyperlog-tui/src/models.rs | 1 + crates/hyperlog-tui/src/shared_engine.rs | 4 ++ 17 files changed, 232 insertions(+), 43 deletions(-) create mode 100644 crates/hyperlog-server/migrations/crdb/20240601104242_add_archive_column_to_nodes.sql create mode 100644 crates/hyperlog-server/src/services/archive.rs create mode 100644 crates/hyperlog-tui/src/commands/archive.rs diff --git a/crates/hyperlog-protos/proto/hyperlog.proto b/crates/hyperlog-protos/proto/hyperlog.proto index 3313043..6ed6694 100644 --- a/crates/hyperlog-protos/proto/hyperlog.proto +++ b/crates/hyperlog-protos/proto/hyperlog.proto @@ -37,6 +37,7 @@ service Graph { rpc CreateItem(CreateItemRequest) returns (CreateItemResponse); rpc UpdateItem(UpdateItemRequest) returns (UpdateItemResponse); rpc ToggleItem(ToggleItemRequest) returns (ToggleItemResponse); + rpc Archive(ArchiveRequest) returns (ArchiveResponse); // Queriers rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse); @@ -76,6 +77,12 @@ message ToggleItemRequest { } message ToggleItemResponse {} +message ArchiveRequest { + string root = 1; + repeated string path = 2; +} +message ArchiveResponse {} + // Queries message GetAvailableRootsRequest {} message GetAvailableRootsResponse { diff --git a/crates/hyperlog-server/migrations/crdb/20240601104242_add_archive_column_to_nodes.sql b/crates/hyperlog-server/migrations/crdb/20240601104242_add_archive_column_to_nodes.sql new file mode 100644 index 0000000..46c5bae --- /dev/null +++ b/crates/hyperlog-server/migrations/crdb/20240601104242_add_archive_column_to_nodes.sql @@ -0,0 +1,3 @@ +-- Add migration script here + +ALTER TABLE nodes ADD COLUMN status VARCHAR(20) DEFAULT 'active' NOT NULL; diff --git a/crates/hyperlog-server/src/commands.rs b/crates/hyperlog-server/src/commands.rs index cb843fa..e4ac6a5 100644 --- a/crates/hyperlog-server/src/commands.rs +++ b/crates/hyperlog-server/src/commands.rs @@ -2,6 +2,7 @@ use hyperlog_core::log::ItemState; use crate::{ services::{ + archive::{self, Archive, ArchiveExt}, create_item::{self, CreateItem, CreateItemExt}, create_root::{self, CreateRoot, CreateRootExt}, create_section::{self, CreateSection, CreateSectionExt}, @@ -43,6 +44,10 @@ pub enum Command { src: Vec, dest: Vec, }, + Archive { + root: String, + path: Vec, + }, } #[allow(dead_code)] @@ -52,6 +57,7 @@ pub struct Commander { create_item: CreateItem, update_item: UpdateItem, toggle_item: ToggleItem, + archive: Archive, } impl Commander { @@ -61,6 +67,7 @@ impl Commander { create_item: CreateItem, update_item: UpdateItem, toggle_item: ToggleItem, + archive: Archive, ) -> Self { Self { create_root, @@ -68,6 +75,7 @@ impl Commander { create_item, update_item, toggle_item, + archive, } } @@ -133,6 +141,13 @@ impl Commander { Ok(()) } Command::Move { .. } => todo!(), + Command::Archive { root, path } => { + self.archive + .execute(archive::Request { root, path }) + .await?; + + Ok(()) + } } } } @@ -149,6 +164,7 @@ impl CommanderExt for SharedState { self.create_item_service(), self.update_item_service(), self.toggle_item_service(), + self.archive_service(), ) } } diff --git a/crates/hyperlog-server/src/external_grpc.rs b/crates/hyperlog-server/src/external_grpc.rs index 9e77536..4a00a53 100644 --- a/crates/hyperlog-server/src/external_grpc.rs +++ b/crates/hyperlog-server/src/external_grpc.rs @@ -380,6 +380,38 @@ impl Graph for Server { Ok(Response::new(ToggleItemResponse {})) } + + async fn archive( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + let req = request.into_inner(); + tracing::trace!("update item: 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(), + )); + } + + self.commander + .execute(Command::Archive { + root: req.root, + path: req.path, + }) + .await + .map_err(to_tonic_err)?; + + Ok(Response::new(ArchiveResponse {})) + } } fn to_native(from: &hyperlog_core::log::GraphItem) -> anyhow::Result { diff --git a/crates/hyperlog-server/src/services.rs b/crates/hyperlog-server/src/services.rs index 87cb249..f310007 100644 --- a/crates/hyperlog-server/src/services.rs +++ b/crates/hyperlog-server/src/services.rs @@ -1,3 +1,4 @@ +pub mod archive; pub mod create_item; pub mod create_root; pub mod create_section; diff --git a/crates/hyperlog-server/src/services/archive.rs b/crates/hyperlog-server/src/services/archive.rs new file mode 100644 index 0000000..4ad1253 --- /dev/null +++ b/crates/hyperlog-server/src/services/archive.rs @@ -0,0 +1,70 @@ +use crate::state::SharedState; + +#[derive(Clone)] +pub struct Archive { + db: sqlx::PgPool, +} + +pub struct Request { + pub root: String, + pub path: Vec, +} +pub struct Response {} + +#[derive(sqlx::FromRow)] +struct Root { + id: uuid::Uuid, +} + +impl Archive { + pub fn new(db: sqlx::PgPool) -> Self { + Self { db } + } + + pub async fn execute(&self, req: Request) -> anyhow::Result { + let Root { id: root_id, .. } = + sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#) + .bind(req.root) + .fetch_one(&self.db) + .await?; + + sqlx::query( + r#" +UPDATE nodes +SET status = 'archive' +WHERE + root_id = $1 + AND path = $2; + "#, + ) + .bind(root_id) + .bind(req.path.join(".")) + .execute(&self.db) + .await?; + + sqlx::query( + r#" +UPDATE nodes +SET status = 'archive' +WHERE root_id = $1 +AND path LIKE $2; + "#, + ) + .bind(root_id) + .bind(format!("{}.%", req.path.join("."))) + .execute(&self.db) + .await?; + + Ok(Response {}) + } +} + +pub trait ArchiveExt { + fn archive_service(&self) -> Archive; +} + +impl ArchiveExt for SharedState { + fn archive_service(&self) -> Archive { + Archive::new(self.db.clone()) + } +} diff --git a/crates/hyperlog-server/src/services/get_graph.rs b/crates/hyperlog-server/src/services/get_graph.rs index 0a011d3..d279b61 100644 --- a/crates/hyperlog-server/src/services/get_graph.rs +++ b/crates/hyperlog-server/src/services/get_graph.rs @@ -60,6 +60,7 @@ impl GetGraph { nodes WHERE root_id = $1 + AND status = 'active' LIMIT 1000 "#, diff --git a/crates/hyperlog-tui/src/app.rs b/crates/hyperlog-tui/src/app.rs index f194db6..bb67710 100644 --- a/crates/hyperlog-tui/src/app.rs +++ b/crates/hyperlog-tui/src/app.rs @@ -92,7 +92,8 @@ impl<'a> App<'a> { Msg::ItemCreated(IOEvent::Success(())) | Msg::ItemUpdated(IOEvent::Success(())) | Msg::SectionCreated(IOEvent::Success(())) - | Msg::ItemToggled(IOEvent::Success(())) => { + | Msg::ItemToggled(IOEvent::Success(())) + | Msg::Archive(IOEvent::Success(())) => { batch.with(self.graph_explorer.new_update_graph()); } Msg::MoveRight => self.graph_explorer.move_right()?, diff --git a/crates/hyperlog-tui/src/commander.rs b/crates/hyperlog-tui/src/commander.rs index 1a9590b..ccaa092 100644 --- a/crates/hyperlog-tui/src/commander.rs +++ b/crates/hyperlog-tui/src/commander.rs @@ -39,6 +39,10 @@ pub enum Command { src: Vec, dest: Vec, }, + Archive { + root: String, + path: Vec, + }, } #[derive(Clone)] diff --git a/crates/hyperlog-tui/src/commander/local.rs b/crates/hyperlog-tui/src/commander/local.rs index 941f1ed..be11b68 100644 --- a/crates/hyperlog-tui/src/commander/local.rs +++ b/crates/hyperlog-tui/src/commander/local.rs @@ -74,6 +74,9 @@ impl Commander { state, }, )?, + Command::Archive { root, path } => self + .engine + .archive(&root, &path.iter().map(|p| p.as_str()).collect::>())?, } self.storage.store(&self.engine)?; diff --git a/crates/hyperlog-tui/src/commander/remote.rs b/crates/hyperlog-tui/src/commander/remote.rs index 9751562..79710a7 100644 --- a/crates/hyperlog-tui/src/commander/remote.rs +++ b/crates/hyperlog-tui/src/commander/remote.rs @@ -27,7 +27,6 @@ impl Commander { let request = tonic::Request::new(CreateRootRequest { root }); let response = client.create_root(request).await?; let res = response.into_inner(); - //self.engine.create_root(&root)?; } Command::CreateSection { root, path } => { let channel = self.channel.clone(); @@ -37,12 +36,6 @@ impl Commander { let request = tonic::Request::new(CreateSectionRequest { root, path }); let response = client.create_section(request).await?; let res = response.into_inner(); - - // self.engine.create( - // &root, - // &path.iter().map(|p| p.as_str()).collect::>(), - // GraphItem::Section(BTreeMap::default()), - // )?; } Command::CreateItem { root, @@ -73,23 +66,9 @@ impl Commander { }); let response = client.create_item(request).await?; let res = response.into_inner(); - // self.engine.create( - // &root, - // &path.iter().map(|p| p.as_str()).collect::>(), - // GraphItem::Item { - // title, - // description, - // state, - // }, - // )? } Command::Move { root, src, dest } => { todo!() - // self.engine.section_move( - // &root, - // &src.iter().map(|p| p.as_str()).collect::>(), - // &dest.iter().map(|p| p.as_str()).collect::>(), - // )? } Command::ToggleItem { root, path } => { let channel = self.channel.clone(); @@ -129,21 +108,18 @@ impl Commander { }); let response = client.update_item(request).await?; let res = response.into_inner(); - // self.engine.update_item( - // &root, - // &path.iter().map(|p| p.as_str()).collect::>(), - // GraphItem::Item { - // title, - // description, - // state, - // }, - // )? + } + Command::Archive { root, path } => { + let channel = self.channel.clone(); + + let mut client = GraphClient::new(channel); + + let request = tonic::Request::new(ArchiveRequest { root, path }); + let response = client.archive(request).await?; + let res = response.into_inner(); } } - // self.storage.store(&self.engine)?; - // self.events.enque_command(cmd)?; - Ok(()) } } diff --git a/crates/hyperlog-tui/src/commands.rs b/crates/hyperlog-tui/src/commands.rs index 60c37df..bee3c9b 100644 --- a/crates/hyperlog-tui/src/commands.rs +++ b/crates/hyperlog-tui/src/commands.rs @@ -2,6 +2,7 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; pub mod batch; +pub mod archive; pub mod create_item; pub mod create_section; pub mod open_update_item_dialog; diff --git a/crates/hyperlog-tui/src/commands/archive.rs b/crates/hyperlog-tui/src/commands/archive.rs new file mode 100644 index 0000000..8cc61ae --- /dev/null +++ b/crates/hyperlog-tui/src/commands/archive.rs @@ -0,0 +1,58 @@ +use hyperlog_core::log::ItemState; +use itertools::Itertools; + +use crate::{ + commander::{self, Commander}, + models::{IOEvent, Msg}, + state::SharedState, +}; + +pub struct ArchiveCommand { + commander: Commander, +} + +impl ArchiveCommand { + pub fn new(commander: Commander) -> Self { + Self { commander } + } + + pub fn command(self, root: &str, path: &[&str]) -> super::Command { + let root = root.to_owned(); + let path = path.iter().map(|s| s.to_string()).collect_vec(); + + super::Command::new(|dispatch| { + tokio::spawn(async move { + dispatch.send(Msg::Archive(IOEvent::Initialized)); + + match self + .commander + .execute(commander::Command::Archive { root, path }) + .await + { + Ok(()) => { + #[cfg(debug_assertions)] + { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + dispatch.send(Msg::Archive(IOEvent::Success(()))); + } + Err(e) => { + dispatch.send(Msg::Archive(IOEvent::Failure(e.to_string()))); + } + } + }); + None + }) + } +} + +pub trait ArchiveCommandExt { + fn archive_command(&self) -> ArchiveCommand; +} + +impl ArchiveCommandExt for SharedState { + fn archive_command(&self) -> ArchiveCommand { + ArchiveCommand::new(self.commander.clone()) + } +} diff --git a/crates/hyperlog-tui/src/components/graph_explorer.rs b/crates/hyperlog-tui/src/components/graph_explorer.rs index ae76113..4edec0f 100644 --- a/crates/hyperlog-tui/src/components/graph_explorer.rs +++ b/crates/hyperlog-tui/src/components/graph_explorer.rs @@ -6,7 +6,7 @@ use ratatui::{prelude::*, widgets::*}; use crate::{ command_parser::Commands, commands::{ - batch::BatchCommand, create_item::CreateItemCommandExt, + archive::ArchiveCommandExt, batch::BatchCommand, create_item::CreateItemCommandExt, create_section::CreateSectionCommandExt, open_update_item_dialog::OpenUpdateItemDialogCommandExt, toggle_item::ToggleItemCommandExt, update_graph::UpdateGraphCommandExt, Command, IntoCommand, @@ -236,7 +236,19 @@ impl<'a> GraphExplorer<'a> { match command { Commands::Archive => { if !self.get_current_path().is_empty() { - tracing::debug!("archiving path: {:?}", self.get_current_path()) + batch.with( + self.state + .archive_command() + .command( + &self.inner.root, + &self + .get_current_path() + .iter() + .map(|i| i.as_str()) + .collect_vec(), + ) + .into_command(), + ); } } Commands::CreateSection { name } => { @@ -244,13 +256,6 @@ impl<'a> GraphExplorer<'a> { let mut path = self.get_current_path(); path.push(name.replace(".", "-")); - // self.state - // .commander - // .execute(commander::Command::CreateSection { - // root: self.inner.root.clone(), - // path, - // })?; - let cmd = self.state.create_section_command().command( &self.inner.root, &path.iter().map(|i| i.as_str()).collect_vec(), diff --git a/crates/hyperlog-tui/src/engine.rs b/crates/hyperlog-tui/src/engine.rs index 6d497fe..fae3108 100644 --- a/crates/hyperlog-tui/src/engine.rs +++ b/crates/hyperlog-tui/src/engine.rs @@ -204,6 +204,12 @@ impl Engine { Some(items) } } + + pub fn archive(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> { + self.delete(root, path)?; + + Ok(()) + } } impl Display for Engine { diff --git a/crates/hyperlog-tui/src/models.rs b/crates/hyperlog-tui/src/models.rs index d56bd18..740a2cf 100644 --- a/crates/hyperlog-tui/src/models.rs +++ b/crates/hyperlog-tui/src/models.rs @@ -27,6 +27,7 @@ pub enum Msg { ItemUpdated(IOEvent<()>), SectionCreated(IOEvent<()>), ItemToggled(IOEvent<()>), + Archive(IOEvent<()>), OpenUpdateItemDialog(IOEvent<()>), } diff --git a/crates/hyperlog-tui/src/shared_engine.rs b/crates/hyperlog-tui/src/shared_engine.rs index b4feb8e..4029e14 100644 --- a/crates/hyperlog-tui/src/shared_engine.rs +++ b/crates/hyperlog-tui/src/shared_engine.rs @@ -69,4 +69,8 @@ impl SharedEngine { pub(crate) fn get_roots(&self) -> Option> { self.inner.read().unwrap().get_roots() } + + pub fn archive(&self, root: &str, path: &[&str]) -> anyhow::Result<()> { + self.inner.write().unwrap().archive(root, path) + } }