@@ -17,6 +17,7 @@ sqlx = { version = "0.7.3", features = ["runtime-tokio", "tls-rustls", "postgres
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
|
||||
serde_json = "1.0.116"
|
||||
bus = "2.4.1"
|
||||
|
||||
[dev-dependencies]
|
||||
similar-asserts = "1.5.0"
|
||||
|
79
crates/hyperlog/src/commander.rs
Normal file
79
crates/hyperlog/src/commander.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use std::{collections::BTreeMap, sync::RwLock};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
engine::Engine,
|
||||
events::Events,
|
||||
log::{GraphItem, ItemState},
|
||||
};
|
||||
|
||||
#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Command {
|
||||
CreateRoot {
|
||||
root: String,
|
||||
},
|
||||
CreateSection {
|
||||
root: String,
|
||||
path: Vec<String>,
|
||||
},
|
||||
CreateItem {
|
||||
root: String,
|
||||
path: Vec<String>,
|
||||
title: String,
|
||||
description: String,
|
||||
state: ItemState,
|
||||
},
|
||||
Move {
|
||||
root: String,
|
||||
src: Vec<String>,
|
||||
dest: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Commander {
|
||||
engine: RwLock<Engine>,
|
||||
events: Events,
|
||||
}
|
||||
|
||||
impl Commander {
|
||||
pub fn execute(&self, cmd: Command) -> anyhow::Result<()> {
|
||||
tracing::debug!("executing event: {}", serde_json::to_string(&cmd)?);
|
||||
|
||||
match cmd {
|
||||
Command::CreateRoot { root } => {
|
||||
self.engine.write().unwrap().create_root(&root)?;
|
||||
}
|
||||
Command::CreateSection { root, path } => {
|
||||
self.engine.write().unwrap().create(
|
||||
&root,
|
||||
&path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
GraphItem::Section(BTreeMap::default()),
|
||||
)?;
|
||||
}
|
||||
Command::CreateItem {
|
||||
root,
|
||||
path,
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
} => self.engine.write().unwrap().create(
|
||||
&root,
|
||||
&path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
GraphItem::Item {
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
},
|
||||
)?,
|
||||
Command::Move { root, src, dest } => self.engine.write().unwrap().section_move(
|
||||
&root,
|
||||
&src.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
&dest.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||
)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
125
crates/hyperlog/src/events.rs
Normal file
125
crates/hyperlog/src/events.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use bus::{Bus, BusReader};
|
||||
|
||||
use crate::commander::Command;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Event {
|
||||
CommandEvent { command: Command },
|
||||
}
|
||||
|
||||
pub struct Events {
|
||||
bus: Mutex<Bus<Event>>,
|
||||
}
|
||||
|
||||
impl Default for Events {
|
||||
fn default() -> Self {
|
||||
let bus = Bus::new(10);
|
||||
|
||||
Self {
|
||||
bus: Mutex::new(bus),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Events {
|
||||
pub fn enque_command(&self, cmd: Command) -> anyhow::Result<()> {
|
||||
self.bus
|
||||
.lock()
|
||||
.unwrap()
|
||||
.broadcast(Event::CommandEvent { command: cmd });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> anyhow::Result<BusReader<Event>> {
|
||||
let rx = self.bus.lock().unwrap().add_rx();
|
||||
|
||||
Ok(rx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use similar_asserts::assert_eq;
|
||||
|
||||
use crate::{commander::Command, events::Event};
|
||||
|
||||
use super::Events;
|
||||
|
||||
#[test]
|
||||
fn can_enque_command() -> anyhow::Result<()> {
|
||||
let events = Events::default();
|
||||
|
||||
events.enque_command(Command::CreateRoot {
|
||||
root: "some-root".into(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_deque_command() -> anyhow::Result<()> {
|
||||
let events = Events::default();
|
||||
let mut rx = events.subscribe()?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
events
|
||||
.enque_command(Command::CreateRoot {
|
||||
root: "some-root".into(),
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let event = rx.recv()?;
|
||||
|
||||
assert_eq!(
|
||||
Event::CommandEvent {
|
||||
command: Command::CreateRoot {
|
||||
root: "some-root".into()
|
||||
},
|
||||
},
|
||||
event
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_broadcast() -> anyhow::Result<()> {
|
||||
let events = Events::default();
|
||||
let mut rx1 = events.subscribe()?;
|
||||
let mut rx2 = events.subscribe()?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
events
|
||||
.enque_command(Command::CreateRoot {
|
||||
root: "some-root".into(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
events
|
||||
.enque_command(Command::CreateRoot {
|
||||
root: "another-event".into(),
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let event = rx1.recv()?;
|
||||
let same_event = rx2.recv()?;
|
||||
|
||||
assert_eq!(event, same_event);
|
||||
|
||||
assert_eq!(
|
||||
Event::CommandEvent {
|
||||
command: Command::CreateRoot {
|
||||
root: "some-root".into()
|
||||
},
|
||||
},
|
||||
event
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum ItemState {
|
||||
#[serde(rename = "not-done")]
|
||||
NotDone,
|
||||
|
@@ -10,7 +10,9 @@ use clap::{Parser, Subcommand};
|
||||
use sqlx::{Pool, Postgres};
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
pub mod commander;
|
||||
pub mod engine;
|
||||
pub mod events;
|
||||
pub mod log;
|
||||
|
||||
#[derive(Parser)]
|
||||
|
Reference in New Issue
Block a user