feat: add archive sub command
Some checks failed
continuous-integration/drone/push Build is failing

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-06-01 13:13:18 +02:00
parent 710fb431f7
commit 9587c60e72
Signed by: kjuulh
GPG Key ID: 9AA7BC13CE474394
17 changed files with 232 additions and 43 deletions

View File

@ -37,6 +37,7 @@ service Graph {
rpc CreateItem(CreateItemRequest) returns (CreateItemResponse); rpc CreateItem(CreateItemRequest) returns (CreateItemResponse);
rpc UpdateItem(UpdateItemRequest) returns (UpdateItemResponse); rpc UpdateItem(UpdateItemRequest) returns (UpdateItemResponse);
rpc ToggleItem(ToggleItemRequest) returns (ToggleItemResponse); rpc ToggleItem(ToggleItemRequest) returns (ToggleItemResponse);
rpc Archive(ArchiveRequest) returns (ArchiveResponse);
// Queriers // Queriers
rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse); rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse);
@ -76,6 +77,12 @@ message ToggleItemRequest {
} }
message ToggleItemResponse {} message ToggleItemResponse {}
message ArchiveRequest {
string root = 1;
repeated string path = 2;
}
message ArchiveResponse {}
// Queries // Queries
message GetAvailableRootsRequest {} message GetAvailableRootsRequest {}
message GetAvailableRootsResponse { message GetAvailableRootsResponse {

View File

@ -0,0 +1,3 @@
-- Add migration script here
ALTER TABLE nodes ADD COLUMN status VARCHAR(20) DEFAULT 'active' NOT NULL;

View File

@ -2,6 +2,7 @@ use hyperlog_core::log::ItemState;
use crate::{ use crate::{
services::{ services::{
archive::{self, Archive, ArchiveExt},
create_item::{self, CreateItem, CreateItemExt}, 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},
@ -43,6 +44,10 @@ pub enum Command {
src: Vec<String>, src: Vec<String>,
dest: Vec<String>, dest: Vec<String>,
}, },
Archive {
root: String,
path: Vec<String>,
},
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -52,6 +57,7 @@ pub struct Commander {
create_item: CreateItem, create_item: CreateItem,
update_item: UpdateItem, update_item: UpdateItem,
toggle_item: ToggleItem, toggle_item: ToggleItem,
archive: Archive,
} }
impl Commander { impl Commander {
@ -61,6 +67,7 @@ impl Commander {
create_item: CreateItem, create_item: CreateItem,
update_item: UpdateItem, update_item: UpdateItem,
toggle_item: ToggleItem, toggle_item: ToggleItem,
archive: Archive,
) -> Self { ) -> Self {
Self { Self {
create_root, create_root,
@ -68,6 +75,7 @@ impl Commander {
create_item, create_item,
update_item, update_item,
toggle_item, toggle_item,
archive,
} }
} }
@ -133,6 +141,13 @@ impl Commander {
Ok(()) Ok(())
} }
Command::Move { .. } => todo!(), 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.create_item_service(),
self.update_item_service(), self.update_item_service(),
self.toggle_item_service(), self.toggle_item_service(),
self.archive_service(),
) )
} }
} }

View File

@ -380,6 +380,38 @@ impl Graph for Server {
Ok(Response::new(ToggleItemResponse {})) Ok(Response::new(ToggleItemResponse {}))
} }
async fn archive(
&self,
request: tonic::Request<ArchiveRequest>,
) -> std::result::Result<tonic::Response<ArchiveResponse>, 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<GraphItem> { fn to_native(from: &hyperlog_core::log::GraphItem) -> anyhow::Result<GraphItem> {

View File

@ -1,3 +1,4 @@
pub mod archive;
pub mod create_item; pub mod create_item;
pub mod create_root; pub mod create_root;
pub mod create_section; pub mod create_section;

View File

@ -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<String>,
}
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<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?;
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())
}
}

View File

@ -60,6 +60,7 @@ impl GetGraph {
nodes nodes
WHERE WHERE
root_id = $1 root_id = $1
AND status = 'active'
LIMIT LIMIT
1000 1000
"#, "#,

View File

@ -92,7 +92,8 @@ impl<'a> App<'a> {
Msg::ItemCreated(IOEvent::Success(())) Msg::ItemCreated(IOEvent::Success(()))
| Msg::ItemUpdated(IOEvent::Success(())) | Msg::ItemUpdated(IOEvent::Success(()))
| Msg::SectionCreated(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()); batch.with(self.graph_explorer.new_update_graph());
} }
Msg::MoveRight => self.graph_explorer.move_right()?, Msg::MoveRight => self.graph_explorer.move_right()?,

View File

@ -39,6 +39,10 @@ pub enum Command {
src: Vec<String>, src: Vec<String>,
dest: Vec<String>, dest: Vec<String>,
}, },
Archive {
root: String,
path: Vec<String>,
},
} }
#[derive(Clone)] #[derive(Clone)]

View File

@ -74,6 +74,9 @@ impl Commander {
state, state,
}, },
)?, )?,
Command::Archive { root, path } => self
.engine
.archive(&root, &path.iter().map(|p| p.as_str()).collect::<Vec<_>>())?,
} }
self.storage.store(&self.engine)?; self.storage.store(&self.engine)?;

View File

@ -27,7 +27,6 @@ impl Commander {
let request = tonic::Request::new(CreateRootRequest { root }); let request = tonic::Request::new(CreateRootRequest { root });
let response = client.create_root(request).await?; let response = client.create_root(request).await?;
let res = response.into_inner(); let res = response.into_inner();
//self.engine.create_root(&root)?;
} }
Command::CreateSection { root, path } => { Command::CreateSection { root, path } => {
let channel = self.channel.clone(); let channel = self.channel.clone();
@ -37,12 +36,6 @@ impl Commander {
let request = tonic::Request::new(CreateSectionRequest { root, path }); 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();
// self.engine.create(
// &root,
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// GraphItem::Section(BTreeMap::default()),
// )?;
} }
Command::CreateItem { Command::CreateItem {
root, root,
@ -73,23 +66,9 @@ impl Commander {
}); });
let response = client.create_item(request).await?; let response = client.create_item(request).await?;
let res = response.into_inner(); let res = response.into_inner();
// self.engine.create(
// &root,
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// GraphItem::Item {
// title,
// description,
// state,
// },
// )?
} }
Command::Move { root, src, dest } => { Command::Move { root, src, dest } => {
todo!() todo!()
// self.engine.section_move(
// &root,
// &src.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// &dest.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
// )?
} }
Command::ToggleItem { root, path } => { Command::ToggleItem { root, path } => {
let channel = self.channel.clone(); let channel = self.channel.clone();
@ -129,21 +108,18 @@ impl Commander {
}); });
let response = client.update_item(request).await?; let response = client.update_item(request).await?;
let res = response.into_inner(); let res = response.into_inner();
// self.engine.update_item( }
// &root, Command::Archive { root, path } => {
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(), let channel = self.channel.clone();
// GraphItem::Item {
// title, let mut client = GraphClient::new(channel);
// description,
// state, 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(()) Ok(())
} }
} }

View File

@ -2,6 +2,7 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
pub mod batch; pub mod batch;
pub mod archive;
pub mod create_item; pub mod create_item;
pub mod create_section; pub mod create_section;
pub mod open_update_item_dialog; pub mod open_update_item_dialog;

View File

@ -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())
}
}

View File

@ -6,7 +6,7 @@ use ratatui::{prelude::*, widgets::*};
use crate::{ use crate::{
command_parser::Commands, command_parser::Commands,
commands::{ commands::{
batch::BatchCommand, create_item::CreateItemCommandExt, archive::ArchiveCommandExt, batch::BatchCommand, create_item::CreateItemCommandExt,
create_section::CreateSectionCommandExt, create_section::CreateSectionCommandExt,
open_update_item_dialog::OpenUpdateItemDialogCommandExt, toggle_item::ToggleItemCommandExt, open_update_item_dialog::OpenUpdateItemDialogCommandExt, toggle_item::ToggleItemCommandExt,
update_graph::UpdateGraphCommandExt, Command, IntoCommand, update_graph::UpdateGraphCommandExt, Command, IntoCommand,
@ -236,7 +236,19 @@ impl<'a> GraphExplorer<'a> {
match command { match command {
Commands::Archive => { Commands::Archive => {
if !self.get_current_path().is_empty() { 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 } => { Commands::CreateSection { name } => {
@ -244,13 +256,6 @@ impl<'a> GraphExplorer<'a> {
let mut path = self.get_current_path(); let mut path = self.get_current_path();
path.push(name.replace(".", "-")); 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( let cmd = self.state.create_section_command().command(
&self.inner.root, &self.inner.root,
&path.iter().map(|i| i.as_str()).collect_vec(), &path.iter().map(|i| i.as_str()).collect_vec(),

View File

@ -204,6 +204,12 @@ impl Engine {
Some(items) Some(items)
} }
} }
pub fn archive(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {
self.delete(root, path)?;
Ok(())
}
} }
impl Display for Engine { impl Display for Engine {

View File

@ -27,6 +27,7 @@ pub enum Msg {
ItemUpdated(IOEvent<()>), ItemUpdated(IOEvent<()>),
SectionCreated(IOEvent<()>), SectionCreated(IOEvent<()>),
ItemToggled(IOEvent<()>), ItemToggled(IOEvent<()>),
Archive(IOEvent<()>),
OpenUpdateItemDialog(IOEvent<()>), OpenUpdateItemDialog(IOEvent<()>),
} }

View File

@ -69,4 +69,8 @@ impl SharedEngine {
pub(crate) fn get_roots(&self) -> Option<Vec<String>> { pub(crate) fn get_roots(&self) -> Option<Vec<String>> {
self.inner.read().unwrap().get_roots() self.inner.read().unwrap().get_roots()
} }
pub fn archive(&self, root: &str, path: &[&str]) -> anyhow::Result<()> {
self.inner.write().unwrap().archive(root, path)
}
} }