From fba1b48951c405d238b12e66c96006b50b0df8ef Mon Sep 17 00:00:00 2001 From: ghassmo Date: Wed, 9 Mar 2022 23:00:41 +0400 Subject: [PATCH] bin/taud: implement add function --- Cargo.lock | 2 + bin/taud/Cargo.toml | 2 + bin/taud/src/main.rs | 164 ++++++++++++++++++++++++++++++++----------- 3 files changed, 126 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a32dc99f6..16e0dfd44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5301,11 +5301,13 @@ dependencies = [ "async-executor", "async-std", "async-trait", + "chrono", "clap 3.0.7", "darkfi", "futures", "log", "num_cpus", + "rand 0.8.5", "serde", "serde_json", "simplelog", diff --git a/bin/taud/Cargo.toml b/bin/taud/Cargo.toml index afbf34628..376c25e27 100644 --- a/bin/taud/Cargo.toml +++ b/bin/taud/Cargo.toml @@ -23,6 +23,8 @@ clap = {version = "3.0.7", features = ["derive"]} log = "0.4.14" num_cpus = "1.13.1" simplelog = "0.11.2" +rand = "0.8.5" +chrono = "0.4.19" # Encoding and parsing serde_json = "1.0.74" diff --git a/bin/taud/src/main.rs b/bin/taud/src/main.rs index 2110c8e45..cb5ca0172 100644 --- a/bin/taud/src/main.rs +++ b/bin/taud/src/main.rs @@ -2,8 +2,10 @@ use std::{fs::create_dir_all, path::PathBuf, sync::Arc}; use async_executor::Executor; use async_trait::async_trait; +use chrono::{TimeZone, Utc}; use clap::{IntoApp, Parser}; use log::debug; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use simplelog::{ColorChoice, TermLogger, TerminalMode}; @@ -34,12 +36,13 @@ pub struct CliTaud { pub verbose: u8, } -#[derive(Clone, Debug, Serialize, Deserialize)] -struct Timestamp { - //XXX change this - time: String, +fn random_ref_id() -> String { + thread_rng().sample_iter(&Alphanumeric).take(30).map(char::from).collect() } +#[derive(Clone, Debug, Serialize, Deserialize)] +struct Timestamp(String); + #[derive(Clone, Debug, Serialize, Deserialize)] struct TauConfig { /// path to dataset @@ -68,22 +71,6 @@ struct Comment { timestamp: Timestamp, } -#[derive(Clone, Debug, Serialize, Deserialize)] -struct TaskInfo { - ref_id: String, - id: u32, - title: String, - desc: String, - assign: String, - project: String, - due: Timestamp, - rank: u32, - created_at: Timestamp, - events: Vec, - comments: Vec, - settings: Settings, -} - #[derive(Clone, Debug, Serialize, Deserialize)] struct MonthTasks { created_at: Timestamp, @@ -91,34 +78,61 @@ struct MonthTasks { task_tks: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize)] +struct TaskInfo { + ref_id: String, + id: u32, + title: String, + desc: String, + assign: Vec, + project: Vec, + due: Option, + rank: u32, + created_at: Timestamp, + events: Vec, + comments: Vec, +} + impl TaskInfo { - pub fn new( - ref_id: String, - id: u32, - title: String, - desc: String, - assign: String, - project: String, - due: Timestamp, - rank: u32, - created_at: Timestamp, - settings: Settings, - ) -> Self { + pub fn new(title: String, desc: String, due: Option, rank: u32) -> Self { + // TODO + // check due date + + // generate ref_id + let ref_id = random_ref_id(); + + // XXX must find the next free id + let mut rng = rand::thread_rng(); + let id: u32 = rng.gen(); + + let created_at: Timestamp = Timestamp(Utc::now().to_string()); + Self { ref_id, id, title, desc, - assign, - project, + assign: vec![], + project: vec![], due, rank, created_at, comments: vec![], events: vec![], - settings, } } + + fn assign(&mut self, n: String) { + self.assign.push(n); + } + + fn project(&mut self, p: String) { + self.project.push(p); + } + + fn save(&self) -> Result<()> { + Ok(()) + } } async fn start(config: TauConfig, executor: Arc>) -> Result<()> { @@ -132,6 +146,8 @@ async fn start(config: TauConfig, executor: Arc>) -> Result<()> { create_dir_all(dataset_path.join("month"))?; create_dir_all(dataset_path.join("task"))?; + let settings = Settings { dataset_path }; + let server_config = RpcServerConfig { socket_addr: config.rpc_listener_url.url.parse()?, use_tls: false, @@ -140,12 +156,14 @@ async fn start(config: TauConfig, executor: Arc>) -> Result<()> { identity_pass: Default::default(), }; - let rpc_interface = Arc::new(JsonRpcInterface {}); + let rpc_interface = Arc::new(JsonRpcInterface { settings }); listen_and_serve(server_config, rpc_interface, executor).await } -struct JsonRpcInterface {} +struct JsonRpcInterface { + settings: Settings, +} #[async_trait] impl RequestHandler for JsonRpcInterface { @@ -157,17 +175,79 @@ impl RequestHandler for JsonRpcInterface { debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap()); match req.method.as_str() { - Some("cmd_add") => return self.cmd_add(req.id, req.params).await, + Some("add") => return self.add(req.id, req.params).await, Some(_) | None => return JsonResult::Err(jsonerr(MethodNotFound, None, req.id)), } } } impl JsonRpcInterface { - // --> {"method": "cmd_add", "params": [String]} - // <-- {"result": "params"} - async fn cmd_add(&self, id: Value, _params: Value) -> JsonResult { - JsonResult::Resp(jsonresp(json!("New task added"), id)) + // RPCAPI: + // Add new task and returns `true` upon success. + // --> {"jsonrpc": "2.0", "method": "add", "params": ["title", "desc", ["assign"], ["project"], "due", "rank"], "id": 1} + // <-- {"jsonrpc": "2.0", "result": true, "id": 1} + async fn add(&self, id: Value, params: Value) -> JsonResult { + let args = params.as_array(); + if args.is_none() { + return JsonResult::Err(jsonerr(InvalidParams, None, id)) + } + let args = args.unwrap(); + + if args.len() != 6 { + return JsonResult::Err(jsonerr(InvalidParams, None, id)) + } + + let mut task: TaskInfo; + + match (args[0].as_str(), args[1].as_str(), args[5].as_u64()) { + (Some(title), Some(desc), Some(rank)) => { + let due: Option = if args[4].as_str().is_some() { + let timestamp = args[4].as_str().unwrap().parse::(); + + if timestamp.is_err() { + return JsonResult::Err(jsonerr( + InvalidParams, + Some("invalid timestamp".into()), + id, + )) + } + + Some(Timestamp(Utc.timestamp(timestamp.unwrap(), 0).to_string())) + } else { + None + }; + + task = TaskInfo::new(title.to_string(), desc.to_string(), due, rank as u32); + } + (None, _, _) => { + return JsonResult::Err(jsonerr(InvalidParams, Some("invalid title".into()), id)) + } + (_, None, _) => { + return JsonResult::Err(jsonerr(InvalidParams, Some("invalid desc".into()), id)) + } + (_, _, None) => { + return JsonResult::Err(jsonerr(InvalidParams, Some("invalid rank".into()), id)) + } + } + + let assign = args[2].as_array(); + if assign.is_some() && assign.unwrap().len() > 0 { + for a in assign.unwrap() { + task.assign(a.as_str().unwrap().into()); + } + } + + let project = args[3].as_array(); + if project.is_some() && project.unwrap().len() > 0 { + for p in project.unwrap() { + task.project(p.as_str().unwrap().into()); + } + } + + match task.save() { + Ok(()) => JsonResult::Resp(jsonresp(json!(true), id)), + Err(e) => JsonResult::Err(jsonerr(ServerError(-32603), Some(e.to_string()), id)), + } } }