From 8a0401989cb25165c5088198c6962ef23edc974d Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Tue, 16 Apr 2024 21:46:44 +0200 Subject: [PATCH] wip --- lib/src/v2.rs | 36 ++++++- server/src/database/v2.rs | 15 +++ server/src/main.rs | 8 +- server/src/routes/api.rs | 167 +---------------------------- server/src/routes/api/session.rs | 167 +++++++++++++++++++++++++++++ server/src/routes/pages.rs | 1 + server/src/routes/pages/dailies.rs | 17 +++ server/templates/dailies.hbs | 14 +++ 8 files changed, 255 insertions(+), 170 deletions(-) create mode 100644 server/src/routes/api/session.rs create mode 100644 server/src/routes/pages/dailies.rs create mode 100644 server/templates/dailies.hbs diff --git a/lib/src/v2.rs b/lib/src/v2.rs index f670360..3d2ad78 100644 --- a/lib/src/v2.rs +++ b/lib/src/v2.rs @@ -6,6 +6,7 @@ pub(self) use uuid::Uuid; pub mod global {} pub mod trees { + // ------- time tracking -------- pub mod category { use super::super::*; @@ -55,9 +56,42 @@ pub mod trees { /// The time when this session was ended pub ended: DateTime, - // FIXME: this field is currently not used /// Whether the item has been "deleted", e.g. it shoudn't be shown in the view + // FIXME: this field is currently not used pub deleted: bool, } } + // ------------------------------------- + + // ------- daily tasks tracking -------- + pub mod daily { + use super::super::*; + + pub const NAME: &str = "DAILIES"; + + pub type K = Uuid; + + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] + pub struct V { + /// The name of the task. + pub name: String, + + // Task should be done every s, e.g. every 3 days or every 1 week. + pub unit_count: u32, + pub unit: TimeUnit, + + /// Whether the item has been "deleted", e.g. it shoudn't be shown in the view + // FIXME: this field is currently not used + pub deleted: bool, + } + + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] + pub enum TimeUnit { + Day, + Week, + Month, + Year, + } + } + // ------------------------------------- } diff --git a/server/src/database/v2.rs b/server/src/database/v2.rs index d301604..cec8fc8 100644 --- a/server/src/database/v2.rs +++ b/server/src/database/v2.rs @@ -57,4 +57,19 @@ pub mod trees { .collect::, _>>()??) } } + + pub mod daily { + use super::super::*; + pub use stl_lib::v2::trees::daily::*; + + pub fn get_all(tree: &sled::Tree) -> Result, StatusJson> { + Ok(tree + .iter() + .map(|result| { + result + .map(|(k, v)| deserialize(&k).and_then(|k| deserialize(&v).map(|v| (k, v)))) + }) + .collect::, _>>()??) + } + } } diff --git a/server/src/main.rs b/server/src/main.rs index 62f1e65..b7d0184 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -89,10 +89,10 @@ async fn main() -> io::Result<()> { routes::api::category::unarchive, routes::api::category::remove_parent, routes::api::category::set_parent, - routes::api::start_session, - routes::api::end_session, - routes::api::edit_session, - routes::api::delete_session, + routes::api::session::start_session, + routes::api::session::end_session, + routes::api::session::edit_session, + routes::api::session::delete_session, routes::api::wait_for_event, auth::login, ], diff --git a/server/src/routes/api.rs b/server/src/routes/api.rs index 2fb95be..cb56c57 100644 --- a/server/src/routes/api.rs +++ b/server/src/routes/api.rs @@ -1,175 +1,12 @@ pub mod category; +pub mod session; use crate::auth::Authorized; -use crate::routes::pages; -use crate::status_json::StatusJson; use crate::util::EventNotifier; -use bincode::{deserialize, serialize}; -use chrono::{Duration, Local, NaiveDateTime, TimeZone}; -use rocket::form::{Form, FromForm}; -use rocket::http::Status; -use rocket::response::Redirect; use rocket::serde::json::Json; -use rocket::serde::uuid::Uuid; -use rocket::{get, post, uri, State}; -use sled::Transactional; +use rocket::{get, State}; use stl_lib::wfe::WaitForEvent; -#[post("/category//start_session")] -pub fn start_session( - _auth: Authorized, - category_uuid: Uuid, - event_notifier: &State, - db: &State, -) -> Result { - toggle_category_session(category_uuid, true, event_notifier, db) -} - -#[post("/category//end_session")] -pub fn end_session( - _auth: Authorized, - category_uuid: Uuid, - event_notifier: &State, - db: &State, -) -> Result { - toggle_category_session(category_uuid, false, event_notifier, db) -} - -pub fn toggle_category_session( - category_uuid: Uuid, - set_active: bool, - event_notifier: &State, - db: &State, -) -> Result { - use crate::database::latest::trees::{category, session}; - - let category_uuid_s = sled::IVec::from(serialize(&category_uuid)?); - - let categories_tree = db.open_tree(category::NAME)?; - let sessions_tree = db.open_tree(session::NAME)?; - - Ok( - (&categories_tree, &sessions_tree).transaction(|(tx_categories, tx_sessions)| { - match tx_categories.get(&category_uuid_s)? { - None => return Ok(Err(Status::NotFound)), - Some(data) => { - let mut category: category::V = deserialize(&data).unwrap(); - let now = Local::now(); - - match (set_active, category.started.take()) { - (false, Some(started)) => { - // only save sessions longer than 5 minutes - let duration = now - started; - if duration > Duration::minutes(5) { - let session_uuid = serialize(&uuid::Uuid::new_v4()).unwrap(); - let session = session::V { - category: category_uuid, - started, - ended: now, - deleted: category.deleted, - }; - tx_sessions.insert(session_uuid, serialize(&session).unwrap())?; - } - } - (true, None) => { - category.started = Some(now); - } - _ => { - // Category is already in the correct state - return Ok(Ok(Status::Ok.into())); - } - } - - tx_categories.insert(&category_uuid_s, serialize(&category).unwrap())?; - event_notifier.notify_event(); - } - } - - Ok(Ok(Status::Ok.into())) - })??, - ) -} - -#[derive(Debug, FromForm)] -pub struct EditSession { - category: Uuid, - started: String, - ended: String, - deleted: bool, -} - -#[post("/session//edit", data = "")] -pub fn edit_session( - _auth: Authorized, - session_uuid: Uuid, - session: Form, - db: &State, -) -> Result { - use crate::database::latest::trees::session; - - let session_uuid_s = sled::IVec::from(serialize(&session_uuid)?); - - let session = session::V { - category: session.category, - started: Local - .from_local_datetime(&NaiveDateTime::parse_from_str( - &session.started, - "%Y-%m-%d %H:%M", - )?) - .unwrap(), - ended: Local - .from_local_datetime(&NaiveDateTime::parse_from_str( - &session.ended, - "%Y-%m-%d %H:%M", - )?) - .unwrap(), - deleted: session.deleted, - }; - - if session.started >= session.ended { - return Err(StatusJson::new( - Status::BadRequest, - "started must be earlier than ended", - )); - } - - db.open_tree(session::NAME)? - .insert(session_uuid_s, serialize(&session)?)?; - - // FIXME: Uuid does not implement FromUriParam for some reason... File an issue? - //Ok(Redirect::to(uri!(pages::session_edit: session_uuid))) - Ok(Redirect::to(format!("/session/{}/edit", session_uuid))) -} - -#[post("/session//delete")] -pub fn delete_session( - _auth: Authorized, - session_uuid: Uuid, - db: &State, -) -> Result { - use crate::database::latest::trees::session; - - let session_uuid_s = sled::IVec::from(serialize(&session_uuid)?); - - let sessions_tree = db.open_tree(session::NAME)?; - - match sessions_tree.remove(session_uuid_s)? { - Some(_) => Ok(Redirect::to(uri!(pages::history))), - None => Err(Status::NotFound.into()), - } - - // TODO: mark as deleted instead of removing - // Ok(sessions_tree.transaction(|tx_sessions| { - // match tx_sessions.get(&session_uuid_s)? { - // None => return Ok(Err(Status::NotFound)), - // Some(data) => { - // let mut session: session::V = deserialize(&data).unwrap(); - // } - // } - // Ok(Ok(Redirect::to(uri!(pages::history)))) - // })??) -} - #[get("/wait_for_event?")] pub async fn wait_for_event( _auth: Authorized, diff --git a/server/src/routes/api/session.rs b/server/src/routes/api/session.rs new file mode 100644 index 0000000..db5cdd6 --- /dev/null +++ b/server/src/routes/api/session.rs @@ -0,0 +1,167 @@ +use crate::auth::Authorized; +use crate::routes::pages; +use crate::status_json::StatusJson; +use crate::util::EventNotifier; +use bincode::{deserialize, serialize}; +use chrono::{Duration, Local, NaiveDateTime, TimeZone}; +use rocket::form::{Form, FromForm}; +use rocket::http::Status; +use rocket::response::Redirect; +use rocket::serde::uuid::Uuid; +use rocket::{post, uri, State}; +use sled::Transactional; + +#[post("/category//start_session")] +pub fn start_session( + _auth: Authorized, + category_uuid: Uuid, + event_notifier: &State, + db: &State, +) -> Result { + toggle_category_session(category_uuid, true, event_notifier, db) +} + +#[post("/category//end_session")] +pub fn end_session( + _auth: Authorized, + category_uuid: Uuid, + event_notifier: &State, + db: &State, +) -> Result { + toggle_category_session(category_uuid, false, event_notifier, db) +} + +pub fn toggle_category_session( + category_uuid: Uuid, + set_active: bool, + event_notifier: &State, + db: &State, +) -> Result { + use crate::database::latest::trees::{category, session}; + + let category_uuid_s = sled::IVec::from(serialize(&category_uuid)?); + + let categories_tree = db.open_tree(category::NAME)?; + let sessions_tree = db.open_tree(session::NAME)?; + + Ok( + (&categories_tree, &sessions_tree).transaction(|(tx_categories, tx_sessions)| { + match tx_categories.get(&category_uuid_s)? { + None => return Ok(Err(Status::NotFound)), + Some(data) => { + let mut category: category::V = deserialize(&data).unwrap(); + let now = Local::now(); + + match (set_active, category.started.take()) { + (false, Some(started)) => { + // only save sessions longer than 5 minutes + let duration = now - started; + if duration > Duration::minutes(5) { + let session_uuid = serialize(&uuid::Uuid::new_v4()).unwrap(); + let session = session::V { + category: category_uuid, + started, + ended: now, + deleted: category.deleted, + }; + tx_sessions.insert(session_uuid, serialize(&session).unwrap())?; + } + } + (true, None) => { + category.started = Some(now); + } + _ => { + // Category is already in the correct state + return Ok(Ok(Status::Ok.into())); + } + } + + tx_categories.insert(&category_uuid_s, serialize(&category).unwrap())?; + event_notifier.notify_event(); + } + } + + Ok(Ok(Status::Ok.into())) + })??, + ) +} + +#[derive(Debug, FromForm)] +pub struct EditSession { + category: Uuid, + started: String, + ended: String, + deleted: bool, +} + +#[post("/session//edit", data = "")] +pub fn edit_session( + _auth: Authorized, + session_uuid: Uuid, + session: Form, + db: &State, +) -> Result { + use crate::database::latest::trees::session; + + let session_uuid_s = sled::IVec::from(serialize(&session_uuid)?); + + let session = session::V { + category: session.category, + started: Local + .from_local_datetime(&NaiveDateTime::parse_from_str( + &session.started, + "%Y-%m-%d %H:%M", + )?) + .unwrap(), + ended: Local + .from_local_datetime(&NaiveDateTime::parse_from_str( + &session.ended, + "%Y-%m-%d %H:%M", + )?) + .unwrap(), + deleted: session.deleted, + }; + + if session.started >= session.ended { + return Err(StatusJson::new( + Status::BadRequest, + "started must be earlier than ended", + )); + } + + db.open_tree(session::NAME)? + .insert(session_uuid_s, serialize(&session)?)?; + + // FIXME: Uuid does not implement FromUriParam for some reason... File an issue? + //Ok(Redirect::to(uri!(pages::session_edit: session_uuid))) + Ok(Redirect::to(format!("/session/{}/edit", session_uuid))) +} + +#[post("/session//delete")] +pub fn delete_session( + _auth: Authorized, + session_uuid: Uuid, + db: &State, +) -> Result { + use crate::database::latest::trees::session; + + let session_uuid_s = sled::IVec::from(serialize(&session_uuid)?); + + let sessions_tree = db.open_tree(session::NAME)?; + + match sessions_tree.remove(session_uuid_s)? { + Some(_) => Ok(Redirect::to(uri!(pages::history))), + None => Err(Status::NotFound.into()), + } + + // TODO: mark as deleted instead of removing + // Ok(sessions_tree.transaction(|tx_sessions| { + // match tx_sessions.get(&session_uuid_s)? { + // None => return Ok(Err(Status::NotFound)), + // Some(data) => { + // let mut session: session::V = deserialize(&data).unwrap(); + // } + // } + // Ok(Ok(Redirect::to(uri!(pages::history)))) + // })??) +} diff --git a/server/src/routes/pages.rs b/server/src/routes/pages.rs index 543d717..ff02fa4 100644 --- a/server/src/routes/pages.rs +++ b/server/src/routes/pages.rs @@ -1,3 +1,4 @@ +pub mod dailies; pub mod stats; pub mod weeks; diff --git a/server/src/routes/pages/dailies.rs b/server/src/routes/pages/dailies.rs new file mode 100644 index 0000000..e2c7ed3 --- /dev/null +++ b/server/src/routes/pages/dailies.rs @@ -0,0 +1,17 @@ +use rocket::{response::content::Html, State}; +use rocket_dyn_templates::Template; +use serde::{Deserialize, Serialize}; + +use crate::{ + auth::Authorized, database::latest::trees::daily::V as Daily, status_json::StatusJson, +}; + +#[get("/")] +pub fn dailies(_auth: Authorized, db: &State) -> Result, StatusJson> { + #[derive(Debug, Serialize, Deserialize)] + struct TemplateContext { + dailies: Vec, + } + + todo!() +} diff --git a/server/templates/dailies.hbs b/server/templates/dailies.hbs new file mode 100644 index 0000000..ca1868d --- /dev/null +++ b/server/templates/dailies.hbs @@ -0,0 +1,14 @@ + + + {{> head}} + + {{> header}} +

Dailies

+ +
+ {{#each categories}} + {{>category_entry}} + {{/each}} +
+ +