wip
This commit is contained in:
@ -6,6 +6,7 @@ pub(self) use uuid::Uuid;
|
|||||||
pub mod global {}
|
pub mod global {}
|
||||||
|
|
||||||
pub mod trees {
|
pub mod trees {
|
||||||
|
// ------- time tracking --------
|
||||||
pub mod category {
|
pub mod category {
|
||||||
use super::super::*;
|
use super::super::*;
|
||||||
|
|
||||||
@ -55,9 +56,42 @@ pub mod trees {
|
|||||||
/// The time when this session was ended
|
/// The time when this session was ended
|
||||||
pub ended: DateTime<Local>,
|
pub ended: DateTime<Local>,
|
||||||
|
|
||||||
// FIXME: this field is currently not used
|
|
||||||
/// Whether the item has been "deleted", e.g. it shoudn't be shown in the view
|
/// 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,
|
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 <unit_count> <unit>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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -------------------------------------
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,4 +57,19 @@ pub mod trees {
|
|||||||
.collect::<Result<Result<_, _>, _>>()??)
|
.collect::<Result<Result<_, _>, _>>()??)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod daily {
|
||||||
|
use super::super::*;
|
||||||
|
pub use stl_lib::v2::trees::daily::*;
|
||||||
|
|
||||||
|
pub fn get_all(tree: &sled::Tree) -> Result<HashMap<K, V>, StatusJson> {
|
||||||
|
Ok(tree
|
||||||
|
.iter()
|
||||||
|
.map(|result| {
|
||||||
|
result
|
||||||
|
.map(|(k, v)| deserialize(&k).and_then(|k| deserialize(&v).map(|v| (k, v))))
|
||||||
|
})
|
||||||
|
.collect::<Result<Result<_, _>, _>>()??)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,10 +89,10 @@ async fn main() -> io::Result<()> {
|
|||||||
routes::api::category::unarchive,
|
routes::api::category::unarchive,
|
||||||
routes::api::category::remove_parent,
|
routes::api::category::remove_parent,
|
||||||
routes::api::category::set_parent,
|
routes::api::category::set_parent,
|
||||||
routes::api::start_session,
|
routes::api::session::start_session,
|
||||||
routes::api::end_session,
|
routes::api::session::end_session,
|
||||||
routes::api::edit_session,
|
routes::api::session::edit_session,
|
||||||
routes::api::delete_session,
|
routes::api::session::delete_session,
|
||||||
routes::api::wait_for_event,
|
routes::api::wait_for_event,
|
||||||
auth::login,
|
auth::login,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,175 +1,12 @@
|
|||||||
pub mod category;
|
pub mod category;
|
||||||
|
pub mod session;
|
||||||
|
|
||||||
use crate::auth::Authorized;
|
use crate::auth::Authorized;
|
||||||
use crate::routes::pages;
|
|
||||||
use crate::status_json::StatusJson;
|
|
||||||
use crate::util::EventNotifier;
|
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::json::Json;
|
||||||
use rocket::serde::uuid::Uuid;
|
use rocket::{get, State};
|
||||||
use rocket::{get, post, uri, State};
|
|
||||||
use sled::Transactional;
|
|
||||||
use stl_lib::wfe::WaitForEvent;
|
use stl_lib::wfe::WaitForEvent;
|
||||||
|
|
||||||
#[post("/category/<category_uuid>/start_session")]
|
|
||||||
pub fn start_session(
|
|
||||||
_auth: Authorized,
|
|
||||||
category_uuid: Uuid,
|
|
||||||
event_notifier: &State<EventNotifier>,
|
|
||||||
db: &State<sled::Db>,
|
|
||||||
) -> Result<StatusJson, StatusJson> {
|
|
||||||
toggle_category_session(category_uuid, true, event_notifier, db)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/category/<category_uuid>/end_session")]
|
|
||||||
pub fn end_session(
|
|
||||||
_auth: Authorized,
|
|
||||||
category_uuid: Uuid,
|
|
||||||
event_notifier: &State<EventNotifier>,
|
|
||||||
db: &State<sled::Db>,
|
|
||||||
) -> Result<StatusJson, StatusJson> {
|
|
||||||
toggle_category_session(category_uuid, false, event_notifier, db)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_category_session(
|
|
||||||
category_uuid: Uuid,
|
|
||||||
set_active: bool,
|
|
||||||
event_notifier: &State<EventNotifier>,
|
|
||||||
db: &State<sled::Db>,
|
|
||||||
) -> Result<StatusJson, StatusJson> {
|
|
||||||
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/<session_uuid>/edit", data = "<session>")]
|
|
||||||
pub fn edit_session(
|
|
||||||
_auth: Authorized,
|
|
||||||
session_uuid: Uuid,
|
|
||||||
session: Form<EditSession>,
|
|
||||||
db: &State<sled::Db>,
|
|
||||||
) -> Result<Redirect, StatusJson> {
|
|
||||||
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/<session_uuid>/delete")]
|
|
||||||
pub fn delete_session(
|
|
||||||
_auth: Authorized,
|
|
||||||
session_uuid: Uuid,
|
|
||||||
db: &State<sled::Db>,
|
|
||||||
) -> Result<Redirect, StatusJson> {
|
|
||||||
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?<timeout>")]
|
#[get("/wait_for_event?<timeout>")]
|
||||||
pub async fn wait_for_event(
|
pub async fn wait_for_event(
|
||||||
_auth: Authorized,
|
_auth: Authorized,
|
||||||
|
|||||||
167
server/src/routes/api/session.rs
Normal file
167
server/src/routes/api/session.rs
Normal file
@ -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/<category_uuid>/start_session")]
|
||||||
|
pub fn start_session(
|
||||||
|
_auth: Authorized,
|
||||||
|
category_uuid: Uuid,
|
||||||
|
event_notifier: &State<EventNotifier>,
|
||||||
|
db: &State<sled::Db>,
|
||||||
|
) -> Result<StatusJson, StatusJson> {
|
||||||
|
toggle_category_session(category_uuid, true, event_notifier, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/category/<category_uuid>/end_session")]
|
||||||
|
pub fn end_session(
|
||||||
|
_auth: Authorized,
|
||||||
|
category_uuid: Uuid,
|
||||||
|
event_notifier: &State<EventNotifier>,
|
||||||
|
db: &State<sled::Db>,
|
||||||
|
) -> Result<StatusJson, StatusJson> {
|
||||||
|
toggle_category_session(category_uuid, false, event_notifier, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_category_session(
|
||||||
|
category_uuid: Uuid,
|
||||||
|
set_active: bool,
|
||||||
|
event_notifier: &State<EventNotifier>,
|
||||||
|
db: &State<sled::Db>,
|
||||||
|
) -> Result<StatusJson, StatusJson> {
|
||||||
|
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/<session_uuid>/edit", data = "<session>")]
|
||||||
|
pub fn edit_session(
|
||||||
|
_auth: Authorized,
|
||||||
|
session_uuid: Uuid,
|
||||||
|
session: Form<EditSession>,
|
||||||
|
db: &State<sled::Db>,
|
||||||
|
) -> Result<Redirect, StatusJson> {
|
||||||
|
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/<session_uuid>/delete")]
|
||||||
|
pub fn delete_session(
|
||||||
|
_auth: Authorized,
|
||||||
|
session_uuid: Uuid,
|
||||||
|
db: &State<sled::Db>,
|
||||||
|
) -> Result<Redirect, StatusJson> {
|
||||||
|
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))))
|
||||||
|
// })??)
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
pub mod dailies;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
pub mod weeks;
|
pub mod weeks;
|
||||||
|
|
||||||
|
|||||||
17
server/src/routes/pages/dailies.rs
Normal file
17
server/src/routes/pages/dailies.rs
Normal file
@ -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<sled::Db>) -> Result<Html<Template>, StatusJson> {
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct TemplateContext {
|
||||||
|
dailies: Vec<Daily>,
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
14
server/templates/dailies.hbs
Normal file
14
server/templates/dailies.hbs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{> head}}
|
||||||
|
<body>
|
||||||
|
{{> header}}
|
||||||
|
<h1>Dailies</h2>
|
||||||
|
|
||||||
|
<div class="category_list">
|
||||||
|
{{#each categories}}
|
||||||
|
{{>category_entry}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user