569 lines
16 KiB
Rust
569 lines
16 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use clap::parser::ValuesRef;
|
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
|
use log::{debug, error, info};
|
|
use rusqlite::Connection;
|
|
use serde::Serialize;
|
|
use simple_logger::SimpleLogger;
|
|
|
|
use db::securities::list_securities;
|
|
|
|
use crate::db::users::{create_user, list_users};
|
|
use crate::migration::apply_migrations;
|
|
use crate::util::{CustomResult, Error, SQLiteConnectionFactory};
|
|
use crate::webui::web_ui_main;
|
|
|
|
mod db;
|
|
mod event_processor;
|
|
mod events;
|
|
mod migration;
|
|
mod services;
|
|
mod util;
|
|
mod webui;
|
|
|
|
#[derive(Debug, Serialize)]
|
|
struct Output<T> {
|
|
status: String,
|
|
result: T,
|
|
}
|
|
|
|
impl<T> Output<T> {
|
|
fn success(shit: T) -> Output<T> {
|
|
return Output {
|
|
status: "success".to_string(),
|
|
result: shit,
|
|
};
|
|
}
|
|
}
|
|
|
|
trait CommandLineUserLogic {
|
|
fn create_user(
|
|
&mut self,
|
|
conn: &mut Connection,
|
|
name: &String,
|
|
public_key: &String,
|
|
) -> CustomResult<()>;
|
|
|
|
fn list_user(&mut self, conn: &Connection) -> CustomResult<()>;
|
|
|
|
fn list_securities(&mut self, conn: &Connection) -> CustomResult<()>;
|
|
|
|
fn process_events(
|
|
&mut self,
|
|
conn: &mut Connection,
|
|
files: ValuesRef<PathBuf>,
|
|
) -> CustomResult<()>;
|
|
|
|
fn run_webui(
|
|
&mut self,
|
|
conn: SQLiteConnectionFactory,
|
|
port: u16,
|
|
enable_template_autoreload: bool,
|
|
) -> CustomResult<()>;
|
|
}
|
|
|
|
fn verbosity_arg() -> Arg {
|
|
return Arg::new("verbosity")
|
|
.short('v')
|
|
.action(ArgAction::Count)
|
|
.default_value("0");
|
|
}
|
|
|
|
fn get_verbosity(m: &ArgMatches) -> log::LevelFilter {
|
|
match m.get_one::<u8>("verbosity") {
|
|
None => log::LevelFilter::Error,
|
|
Some(1) => log::LevelFilter::Warn,
|
|
Some(2) => log::LevelFilter::Info,
|
|
Some(3) => log::LevelFilter::Debug,
|
|
Some(4) => log::LevelFilter::Trace,
|
|
_ => log::LevelFilter::Error,
|
|
}
|
|
}
|
|
|
|
fn db_path_arg() -> Arg {
|
|
return Arg::new("db-path")
|
|
.long("db-path")
|
|
.value_name("FILE")
|
|
.value_parser(value_parser!(PathBuf))
|
|
.default_value("ledger.sqlite");
|
|
}
|
|
|
|
fn get_db_path(m: &ArgMatches) -> &PathBuf {
|
|
return m.get_one::<PathBuf>("db-path").unwrap();
|
|
}
|
|
|
|
fn new_user_command_line_parser() -> Command {
|
|
return Command::new("new")
|
|
.long_flag("new")
|
|
.about("Create new user.")
|
|
.arg(
|
|
Arg::new("name")
|
|
.long("name")
|
|
.required(true)
|
|
.help("name of new user.")
|
|
.action(ArgAction::Set),
|
|
)
|
|
.arg(
|
|
Arg::new("public_key")
|
|
.required(true)
|
|
.long("public-key")
|
|
.help("public key of users.")
|
|
.action(ArgAction::Set),
|
|
);
|
|
}
|
|
|
|
fn ls_user_command_line_parser() -> Command {
|
|
return Command::new("ls").long_flag("ls").about("List all users.");
|
|
}
|
|
|
|
fn process_events_command_line_parser() -> Command {
|
|
Command::new("process")
|
|
.about("Process events one after the other. In the order received")
|
|
.arg_required_else_help(true)
|
|
.arg(clap::arg!(<PATH> ... "Stuff to add").value_parser(value_parser!(PathBuf)))
|
|
}
|
|
|
|
fn user_command_line_parser() -> Command {
|
|
return Command::new("users")
|
|
.about("Handy command about users.")
|
|
.subcommand(new_user_command_line_parser())
|
|
.subcommand(ls_user_command_line_parser())
|
|
.arg_required_else_help(true);
|
|
}
|
|
|
|
fn event_command_line_parser() -> Command {
|
|
return Command::new("events")
|
|
.about("Process and query events files")
|
|
.subcommand(process_events_command_line_parser())
|
|
.arg_required_else_help(true);
|
|
}
|
|
|
|
fn ls_securities_command_line_parser() -> Command {
|
|
return Command::new("ls")
|
|
.long_flag("ls")
|
|
.about("List all securities.");
|
|
}
|
|
|
|
fn run_web_ui_command_line_parser() -> Command {
|
|
return Command::new("run")
|
|
.arg(
|
|
Arg::new("port")
|
|
.long("port")
|
|
.value_parser(clap::value_parser!(u16).range(2000..))
|
|
.action(ArgAction::Set)
|
|
.default_value("2222"),
|
|
)
|
|
.arg(
|
|
Arg::new("enable-template-autoreload")
|
|
.long("enable-template-autoreload")
|
|
.required(false)
|
|
.num_args(0)
|
|
.help("Reload underlying template (for development)"),
|
|
)
|
|
.about("Run web user interface.");
|
|
}
|
|
|
|
fn securities_command_line_parser() -> Command {
|
|
return Command::new("securities")
|
|
.about("Handy command about securities.")
|
|
.subcommand(ls_securities_command_line_parser())
|
|
.arg_required_else_help(true);
|
|
}
|
|
|
|
fn web_ui_command_line_parser() -> Command {
|
|
return Command::new("wui")
|
|
.about("Web user interface.")
|
|
.subcommand(run_web_ui_command_line_parser())
|
|
.arg_required_else_help(true);
|
|
}
|
|
|
|
fn command_line_parser() -> Command {
|
|
return Command::new("prog")
|
|
.arg(verbosity_arg())
|
|
.arg(db_path_arg())
|
|
.subcommand(event_command_line_parser())
|
|
.subcommand(user_command_line_parser())
|
|
.subcommand(securities_command_line_parser())
|
|
.subcommand(web_ui_command_line_parser())
|
|
.arg_required_else_help(true);
|
|
}
|
|
|
|
fn process_new_user_command_line(
|
|
conn: &mut Connection,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing new user command line {:?}.", m);
|
|
let name = m.get_one::<String>("name").unwrap();
|
|
let pk = m.get_one::<String>("public_key").unwrap();
|
|
logic.create_user(conn, name, pk)
|
|
}
|
|
|
|
fn process_list_user_command_line(
|
|
conn: &Connection,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing list users command line {:?}.", m);
|
|
logic.list_user(conn)
|
|
}
|
|
|
|
fn process_user_command_line(
|
|
conn: &mut Connection,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing user command line {:?}.", m);
|
|
match m.subcommand() {
|
|
Some(("new", new_user_match)) => process_new_user_command_line(conn, new_user_match, logic),
|
|
Some(("ls", list_user_match)) => {
|
|
process_list_user_command_line(conn, list_user_match, logic)
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn process_list_securities_command_line(
|
|
conn: &Connection,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing list securities command line {:?}.", m);
|
|
logic.list_securities(conn)
|
|
}
|
|
|
|
fn process_process_events_command_line(
|
|
conn: &mut Connection,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing process event command line {:?}.", m);
|
|
let mut files = m.get_many::<PathBuf>("PATH").unwrap();
|
|
if files.any(|x| !x.exists()) {
|
|
error!("Some passed files do not exists. Existing right now.");
|
|
return Err(Error::UserInputError(
|
|
"Some passed files do not exists. Existing right now.".to_string(),
|
|
));
|
|
}
|
|
|
|
logic.process_events(conn, files)
|
|
}
|
|
|
|
fn process_run_wui_command_line(
|
|
conn_fact: SQLiteConnectionFactory,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing run wui command line {:?}.", m);
|
|
let port = m.get_one::<u16>("port").unwrap_or(&2222);
|
|
let enable_template_autoreload = m.get_flag("enable-template-autoreload");
|
|
logic.run_webui(conn_fact, *port, enable_template_autoreload)
|
|
}
|
|
|
|
fn process_securities_command_line(
|
|
conn: &Connection,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing securities command line {:?}.", m);
|
|
match m.subcommand() {
|
|
Some(("ls", list_securities_match)) => {
|
|
process_list_securities_command_line(conn, list_securities_match, logic)
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn process_events_command_line(
|
|
conn: &mut Connection,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing events command line {:?}.", m);
|
|
match m.subcommand() {
|
|
Some(("process", process_events_match)) => {
|
|
process_process_events_command_line(conn, process_events_match, logic)
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn process_wui_command_line(
|
|
conn_fact: SQLiteConnectionFactory,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing wui command line {:?}.", m);
|
|
match m.subcommand() {
|
|
Some(("run", run_wui_match)) => {
|
|
process_run_wui_command_line(conn_fact, run_wui_match, logic)
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn process_command_line(
|
|
conn_fact: SQLiteConnectionFactory,
|
|
m: &ArgMatches,
|
|
logic: &mut dyn CommandLineUserLogic,
|
|
) -> CustomResult<()> {
|
|
debug!("Processing command line {:?}.", m);
|
|
let mut conn = conn_fact.connection()?;
|
|
match m.subcommand() {
|
|
Some(("users", user_match)) => process_user_command_line(&mut conn, user_match, logic),
|
|
Some(("securities", securities_match)) => {
|
|
process_securities_command_line(&mut conn, securities_match, logic)
|
|
}
|
|
Some(("events", events_match)) => {
|
|
process_events_command_line(&mut conn, events_match, logic)
|
|
}
|
|
Some(("wui", wui_match)) => process_wui_command_line(conn_fact, wui_match, logic),
|
|
_ => {
|
|
println!("Received un parsable command. {:?}", m.subcommand());
|
|
unreachable!();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod cli_test {
|
|
use super::*;
|
|
|
|
struct TestCommandLineLogic {
|
|
list_user_count: usize,
|
|
create_user_count: usize,
|
|
list_securities_count: usize,
|
|
process_events_count: usize,
|
|
run_webui_count: usize,
|
|
}
|
|
|
|
impl TestCommandLineLogic {
|
|
fn new() -> TestCommandLineLogic {
|
|
return TestCommandLineLogic {
|
|
list_user_count: 0,
|
|
create_user_count: 0,
|
|
list_securities_count: 0,
|
|
process_events_count: 0,
|
|
run_webui_count: 0,
|
|
};
|
|
}
|
|
}
|
|
|
|
impl CommandLineUserLogic for TestCommandLineLogic {
|
|
fn create_user(
|
|
&mut self,
|
|
_conn: &mut Connection,
|
|
_name: &String,
|
|
_public_key: &String,
|
|
) -> CustomResult<()> {
|
|
self.create_user_count += 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn list_user(&mut self, _conn: &Connection) -> CustomResult<()> {
|
|
self.list_user_count += 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn list_securities(&mut self, _conn: &Connection) -> CustomResult<()> {
|
|
self.list_securities_count += 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn process_events(
|
|
&mut self,
|
|
_conn: &mut Connection,
|
|
_files: ValuesRef<PathBuf>,
|
|
) -> CustomResult<()> {
|
|
self.process_events_count += 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn run_webui(
|
|
&mut self,
|
|
_conn: SQLiteConnectionFactory,
|
|
_port: u16,
|
|
_enable_template_autoreload: bool,
|
|
) -> CustomResult<()> {
|
|
self.run_webui_count += 1;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn verbosity() {
|
|
{
|
|
let cmd = Command::new("test").arg(verbosity_arg());
|
|
let m = cmd.get_matches_from(vec!["test", "-vv"]);
|
|
assert_eq!(get_verbosity(&m), log::LevelFilter::Info);
|
|
}
|
|
|
|
{
|
|
let cmd = Command::new("test").arg(verbosity_arg());
|
|
let m = cmd.get_matches_from(vec!["test"]);
|
|
assert_eq!(get_verbosity(&m), log::LevelFilter::Error);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn db_path() {
|
|
{
|
|
let cmd = Command::new("test").arg(db_path_arg());
|
|
let m = cmd.get_matches_from(vec!["test", "--db-path", "/foo/bar"]);
|
|
let expt = PathBuf::from("/foo/bar");
|
|
assert_eq!(get_db_path(&m), &expt);
|
|
}
|
|
|
|
{
|
|
let cmd = Command::new("test").arg(db_path_arg());
|
|
let m = cmd.get_matches_from(vec!["test"]);
|
|
let expt = PathBuf::from("ledger.sqlite");
|
|
assert_eq!(get_db_path(&m), &expt);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn create_user() -> CustomResult<()> {
|
|
let cmd = command_line_parser();
|
|
let m = cmd.get_matches_from(vec![
|
|
"test",
|
|
"users",
|
|
"new",
|
|
"--name",
|
|
"paul",
|
|
"--public-key",
|
|
"123abc",
|
|
]);
|
|
let mut logic = TestCommandLineLogic::new();
|
|
let mut conn = Connection::open_in_memory().unwrap();
|
|
process_command_line(SQLiteConnectionFactory::in_memory(), &m, &mut logic)?;
|
|
assert_eq!(logic.create_user_count, 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn list_users() -> CustomResult<()> {
|
|
let cmd = command_line_parser();
|
|
let m = cmd.get_matches_from(vec!["test", "users", "ls"]);
|
|
let mut logic = TestCommandLineLogic::new();
|
|
let mut conn = Connection::open_in_memory().unwrap();
|
|
process_command_line(SQLiteConnectionFactory::in_memory(), &m, &mut logic)?;
|
|
assert_eq!(logic.list_user_count, 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn list_securities() -> CustomResult<()> {
|
|
let cmd = command_line_parser();
|
|
let m = cmd.get_matches_from(vec!["test", "securities", "ls"]);
|
|
let mut logic = TestCommandLineLogic::new();
|
|
let mut conn = Connection::open_in_memory().unwrap();
|
|
process_command_line(SQLiteConnectionFactory::in_memory(), &m, &mut logic)?;
|
|
assert_eq!(logic.list_securities_count, 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn process_events() -> CustomResult<()> {
|
|
let cmd = command_line_parser();
|
|
let m = cmd.get_matches_from(vec!["test", "events", "process", "/dev/null"]);
|
|
let mut logic = TestCommandLineLogic::new();
|
|
let mut conn = Connection::open_in_memory().unwrap();
|
|
process_command_line(SQLiteConnectionFactory::in_memory(), &m, &mut logic)?;
|
|
assert_eq!(logic.process_events_count, 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn run_web_ui() -> CustomResult<()> {
|
|
let cmd = command_line_parser();
|
|
let m = cmd.get_matches_from(vec!["test", "wui", "run"]);
|
|
let mut logic = TestCommandLineLogic::new();
|
|
let mut conn = Connection::open_in_memory().unwrap();
|
|
process_command_line(SQLiteConnectionFactory::in_memory(), &m, &mut logic)?;
|
|
assert_eq!(logic.run_webui_count, 1);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
struct ProCommandLineLogic {}
|
|
|
|
impl ProCommandLineLogic {
|
|
fn new() -> ProCommandLineLogic {
|
|
return ProCommandLineLogic {};
|
|
}
|
|
}
|
|
|
|
impl CommandLineUserLogic for ProCommandLineLogic {
|
|
fn create_user(
|
|
&mut self,
|
|
conn: &mut Connection,
|
|
name: &String,
|
|
public_key: &String,
|
|
) -> CustomResult<()> {
|
|
let tx = conn.transaction().map_err(Error::SqlError)?;
|
|
create_user(&tx, name, public_key)?;
|
|
tx.commit().map_err(Error::SqlError)
|
|
}
|
|
|
|
fn list_user(&mut self, conn: &Connection) -> CustomResult<()> {
|
|
let users = list_users(conn)?;
|
|
let out = Output::success(users);
|
|
println!("{}", serde_json::to_string(&out).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn list_securities(&mut self, conn: &Connection) -> CustomResult<()> {
|
|
let securities = list_securities(conn)?;
|
|
let out = Output::success(securities);
|
|
println!("{}", serde_json::to_string(&out).unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn process_events(
|
|
&mut self,
|
|
conn: &mut Connection,
|
|
files: ValuesRef<PathBuf>,
|
|
) -> CustomResult<()> {
|
|
// process_event_files(conn, files)
|
|
Ok(())
|
|
}
|
|
|
|
fn run_webui(
|
|
&mut self,
|
|
conn_fact: SQLiteConnectionFactory,
|
|
port: u16,
|
|
enable_template_autoreload: bool,
|
|
) -> CustomResult<()> {
|
|
info!("Serving web-ui on port {:?}", port);
|
|
web_ui_main(conn_fact, port, enable_template_autoreload)
|
|
}
|
|
}
|
|
|
|
fn get_connection(m: &ArgMatches) -> SQLiteConnectionFactory {
|
|
let path = get_db_path(&m);
|
|
|
|
info!("Sqlite database location is {:?}.", path);
|
|
SQLiteConnectionFactory::new(path.clone())
|
|
}
|
|
|
|
fn set_logger(m: &ArgMatches) {
|
|
SimpleLogger::new()
|
|
.with_level(get_verbosity(&m))
|
|
.init()
|
|
.unwrap();
|
|
}
|
|
|
|
fn main() -> CustomResult<()> {
|
|
let command = command_line_parser();
|
|
let m = command.get_matches();
|
|
|
|
set_logger(&m);
|
|
let conn_factory = get_connection(&m);
|
|
|
|
apply_migrations(&mut conn_factory.connection()?).unwrap();
|
|
let mut logic = ProCommandLineLogic::new();
|
|
process_command_line(conn_factory, &m, &mut logic)
|
|
}
|