This commit is contained in:
2024-04-16 21:46:44 +02:00
parent 5c11d5d091
commit 8a0401989c
8 changed files with 255 additions and 170 deletions

View File

@ -57,4 +57,19 @@ pub mod trees {
.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<_, _>, _>>()??)
}
}
}

View File

@ -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,
],

View File

@ -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/<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>")]
pub async fn wait_for_event(
_auth: Authorized,

View 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))))
// })??)
}

View File

@ -1,3 +1,4 @@
pub mod dailies;
pub mod stats;
pub mod weeks;

View 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!()
}