Some refactoring
This commit is contained in:
@ -1,10 +1,9 @@
|
||||
pub(self) use chrono::NaiveDateTime;
|
||||
pub(self) use serde_derive::{Serialize, Deserialize};
|
||||
pub(self) use serde_derive::{Deserialize, Serialize};
|
||||
pub(self) use uuid::Uuid;
|
||||
|
||||
/// Stuff in the default namespace
|
||||
pub mod global {
|
||||
}
|
||||
pub mod global {}
|
||||
|
||||
pub mod trees {
|
||||
pub mod categories {
|
||||
@ -42,5 +41,3 @@ pub mod trees {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,26 @@
|
||||
use rocket_contrib::templates::Engines;
|
||||
use chrono::NaiveDateTime;
|
||||
use handlebars::handlebars_helper;
|
||||
use rocket_contrib::templates::Engines;
|
||||
|
||||
pub fn register_helpers(engines: &mut Engines) {
|
||||
handlebars_helper!(pretty_datetime: |dt: str| {
|
||||
let date = dt.trim_end_matches(|c| c != 'T').trim_end_matches('T');
|
||||
let time = dt.trim_start_matches(|c| c != 'T').trim_start_matches('T')
|
||||
.trim_end_matches(|c| c != ':').trim_end_matches(':');
|
||||
format!("{} {}", date, time)
|
||||
let dt: NaiveDateTime = dt.parse().unwrap();
|
||||
format!("{}", dt.format("%Y-%m-%d %H:%M"))
|
||||
});
|
||||
engines
|
||||
.handlebars
|
||||
.register_helper("pretty_datetime", Box::new(pretty_datetime));
|
||||
|
||||
engines.handlebars.register_helper("pretty_datetime", Box::new(pretty_datetime));
|
||||
handlebars_helper!(pretty_seconds: |secs: u64| {
|
||||
let minutes = secs / 60;
|
||||
let hours = minutes / 60;
|
||||
if hours > 0 {
|
||||
format!("{}h {}m", hours, minutes)
|
||||
} else {
|
||||
format!("{}m", minutes)
|
||||
}
|
||||
});
|
||||
engines
|
||||
.handlebars
|
||||
.register_helper("pretty_seconds", Box::new(pretty_seconds));
|
||||
}
|
||||
|
||||
21
src/main.rs
21
src/main.rs
@ -1,13 +1,13 @@
|
||||
#![feature(decl_macro)]
|
||||
mod database;
|
||||
mod handlebars_util;
|
||||
mod routes;
|
||||
mod status_json;
|
||||
mod handlebars_util;
|
||||
|
||||
use std::{io, env};
|
||||
use dotenv::dotenv;
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket_contrib::serve::StaticFiles;
|
||||
use rocket_contrib::templates::Template;
|
||||
use std::{env, io};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
dotenv().ok();
|
||||
@ -17,17 +17,19 @@ fn main() -> io::Result<()> {
|
||||
let sled = sled::open(db_path)?;
|
||||
|
||||
let rocket = rocket::ignite()
|
||||
.attach(Template::custom(|engines| handlebars_util::register_helpers(engines)))
|
||||
.attach(Template::custom(|engines| {
|
||||
handlebars_util::register_helpers(engines)
|
||||
}))
|
||||
.manage(sled)
|
||||
.mount("/static", StaticFiles::from("static"))
|
||||
.mount(
|
||||
"/",
|
||||
rocket::routes![
|
||||
routes::index,
|
||||
routes::history,
|
||||
routes::create_category,
|
||||
routes::activate_category,
|
||||
routes::deactivate_category,
|
||||
routes::pages::index,
|
||||
routes::pages::history,
|
||||
routes::api::create_category,
|
||||
routes::api::activate_category,
|
||||
routes::api::deactivate_category,
|
||||
],
|
||||
);
|
||||
|
||||
@ -35,4 +37,3 @@ fn main() -> io::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
140
src/routes.rs
140
src/routes.rs
@ -1,140 +0,0 @@
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
use rocket::{State, get, post, uri};
|
||||
use uuid::Uuid;
|
||||
use rocket_contrib::templates::Template;
|
||||
use crate::database::v1::trees::{categories, past_sessions};
|
||||
use crate::status_json::StatusJson;
|
||||
use rocket::http::Status;
|
||||
use bincode::{serialize, deserialize};
|
||||
use rocket::response::Redirect;
|
||||
use rocket::request::{FromForm, Form};
|
||||
use sled::Transactional;
|
||||
use chrono::{Duration, Utc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct NewCategory {
|
||||
name: String,
|
||||
color: String,
|
||||
}
|
||||
|
||||
#[post("/create_category", data="<category>")]
|
||||
pub fn create_category(category: Form<NewCategory>, db: State<'_, sled::Db>) -> Result<Redirect, StatusJson> {
|
||||
let category = category.into_inner();
|
||||
|
||||
let categories_tree = db.open_tree(categories::NAME)?;
|
||||
categories_tree.insert(serialize(&Uuid::new_v4())?, serialize(&categories::V {
|
||||
name: category.name,
|
||||
color: category.color,
|
||||
started: None,
|
||||
})?)?;
|
||||
|
||||
Ok(Redirect::to(uri!(index)))
|
||||
}
|
||||
|
||||
|
||||
#[post("/set_category/<category_uuid>/active")]
|
||||
pub fn activate_category(category_uuid: String, db: State<'_, sled::Db>) -> Result<StatusJson, StatusJson> {
|
||||
toggle_category(category_uuid, true, db)
|
||||
}
|
||||
|
||||
#[post("/set_category/<category_uuid>/inactive")]
|
||||
pub fn deactivate_category(category_uuid: String, db: State<'_, sled::Db>) -> Result<StatusJson, StatusJson> {
|
||||
toggle_category(category_uuid, false, db)
|
||||
}
|
||||
|
||||
pub fn toggle_category(category_uuid: String, set_active: bool, db: State<'_, sled::Db>) -> Result<StatusJson, StatusJson> {
|
||||
let category_uuid = Uuid::parse_str(&category_uuid)?;
|
||||
let category_uuid_s = sled::IVec::from(serialize(&category_uuid)?);
|
||||
|
||||
let categories_tree = db.open_tree(categories::NAME)?;
|
||||
let past_sessions_tree = db.open_tree(past_sessions::NAME)?;
|
||||
|
||||
Ok((&categories_tree, &past_sessions_tree)
|
||||
.transaction(|(tx_categories, tx_past_sessions)| {
|
||||
match tx_categories.get(&category_uuid_s)? {
|
||||
None => return Ok(Err(Status::NotFound)),
|
||||
Some(data) => {
|
||||
|
||||
let mut category: categories::V = dbg!(deserialize(&data).unwrap());
|
||||
let now = Utc::now().naive_utc();
|
||||
|
||||
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::new_v4()).unwrap();
|
||||
let past_session = past_sessions::V {
|
||||
category: category_uuid,
|
||||
started,
|
||||
ended: now,
|
||||
};
|
||||
tx_past_sessions.insert(session_uuid, serialize(&past_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())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Ok(Status::Ok.into()))
|
||||
})??)
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub fn index(db: State<'_, sled::Db>) -> Result<Template, StatusJson> {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct TemplateContext {
|
||||
categories: Vec<(Uuid, categories::V)>,
|
||||
}
|
||||
|
||||
let categories_tree = db.open_tree(categories::NAME)?;
|
||||
|
||||
let context = TemplateContext {
|
||||
categories: categories_tree.iter()
|
||||
.map(|result| result.map(|(k, v)| deserialize(&k).and_then(|k| deserialize(&v).map(|v| (k, v)))))
|
||||
.collect::<Result<Result<_, _>, _>>()??,
|
||||
};
|
||||
|
||||
Ok(Template::render("index", dbg!(&context)))
|
||||
}
|
||||
|
||||
#[get("/history")]
|
||||
pub fn history(db: State<'_, sled::Db>) -> Result<Template, StatusJson> {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct TemplateContext {
|
||||
sessions: Vec<(categories::V, past_sessions::V)>,
|
||||
}
|
||||
|
||||
let categories_tree = db.open_tree(categories::NAME)?;
|
||||
let past_sessions_tree = db.open_tree(past_sessions::NAME)?;
|
||||
|
||||
let categories: HashMap<categories::K, categories::V> = categories_tree.iter()
|
||||
.map(|result| result.map(|(k, v)| deserialize(&k).and_then(|k| deserialize(&v).map(|v| (k, v)))))
|
||||
.collect::<Result<Result<_, _>, _>>()??;
|
||||
|
||||
let past_sessions: HashMap<past_sessions::K, past_sessions::V> = past_sessions_tree.iter()
|
||||
.map(|result| result.map(|(k, v)| deserialize(&k).and_then(|k| deserialize(&v).map(|v| (k, v)))))
|
||||
.collect::<Result<Result<_, _>, _>>()??;
|
||||
|
||||
|
||||
let context = TemplateContext {
|
||||
sessions: past_sessions.into_iter()
|
||||
.map(|(_, session)| {
|
||||
let category = categories.get(&session.category).unwrap().clone();
|
||||
(category, session)
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Ok(Template::render("history", dbg!(&context)))
|
||||
}
|
||||
103
src/routes/api.rs
Normal file
103
src/routes/api.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use crate::database::v1::trees::{categories, past_sessions};
|
||||
use crate::status_json::StatusJson;
|
||||
use bincode::{deserialize, serialize};
|
||||
use chrono::{Duration, Utc};
|
||||
use rocket::http::Status;
|
||||
use rocket::request::{Form, FromForm};
|
||||
use rocket::{post, State};
|
||||
use sled::Transactional;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct NewCategory {
|
||||
name: String,
|
||||
color: String,
|
||||
}
|
||||
|
||||
#[post("/create_category", data = "<category>")]
|
||||
pub fn create_category(
|
||||
category: Form<NewCategory>,
|
||||
db: State<'_, sled::Db>,
|
||||
) -> Result<StatusJson, StatusJson> {
|
||||
let category = category.into_inner();
|
||||
|
||||
let categories_tree = db.open_tree(categories::NAME)?;
|
||||
categories_tree.insert(
|
||||
serialize(&Uuid::new_v4())?,
|
||||
serialize(&categories::V {
|
||||
name: category.name,
|
||||
color: category.color,
|
||||
started: None,
|
||||
})?,
|
||||
)?;
|
||||
|
||||
Ok(Status::Ok.into())
|
||||
}
|
||||
|
||||
#[post("/set_category/<category_uuid>/active")]
|
||||
pub fn activate_category(
|
||||
category_uuid: String,
|
||||
db: State<'_, sled::Db>,
|
||||
) -> Result<StatusJson, StatusJson> {
|
||||
toggle_category(category_uuid, true, db)
|
||||
}
|
||||
|
||||
#[post("/set_category/<category_uuid>/inactive")]
|
||||
pub fn deactivate_category(
|
||||
category_uuid: String,
|
||||
db: State<'_, sled::Db>,
|
||||
) -> Result<StatusJson, StatusJson> {
|
||||
toggle_category(category_uuid, false, db)
|
||||
}
|
||||
|
||||
pub fn toggle_category(
|
||||
category_uuid: String,
|
||||
set_active: bool,
|
||||
db: State<'_, sled::Db>,
|
||||
) -> Result<StatusJson, StatusJson> {
|
||||
let category_uuid = Uuid::parse_str(&category_uuid)?;
|
||||
let category_uuid_s = sled::IVec::from(serialize(&category_uuid)?);
|
||||
|
||||
let categories_tree = db.open_tree(categories::NAME)?;
|
||||
let past_sessions_tree = db.open_tree(past_sessions::NAME)?;
|
||||
|
||||
Ok((&categories_tree, &past_sessions_tree).transaction(
|
||||
|(tx_categories, tx_past_sessions)| {
|
||||
match tx_categories.get(&category_uuid_s)? {
|
||||
None => return Ok(Err(Status::NotFound)),
|
||||
Some(data) => {
|
||||
let mut category: categories::V = deserialize(&data).unwrap();
|
||||
let now = Utc::now().naive_utc();
|
||||
|
||||
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::new_v4()).unwrap();
|
||||
let past_session = past_sessions::V {
|
||||
category: category_uuid,
|
||||
started,
|
||||
ended: now,
|
||||
};
|
||||
tx_past_sessions
|
||||
.insert(session_uuid, serialize(&past_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())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Ok(Status::Ok.into()))
|
||||
},
|
||||
)??)
|
||||
}
|
||||
2
src/routes/mod.rs
Normal file
2
src/routes/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod api;
|
||||
pub mod pages;
|
||||
82
src/routes/pages.rs
Normal file
82
src/routes/pages.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use crate::database::v1::trees::{categories, past_sessions};
|
||||
use crate::status_json::StatusJson;
|
||||
use bincode::deserialize;
|
||||
use rocket::{get, State};
|
||||
use rocket_contrib::templates::Template;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[get("/")]
|
||||
pub fn index(db: State<'_, sled::Db>) -> Result<Template, StatusJson> {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct TemplateContext {
|
||||
categories: Vec<(Uuid, categories::V)>,
|
||||
}
|
||||
|
||||
let categories_tree = db.open_tree(categories::NAME)?;
|
||||
|
||||
let context = TemplateContext {
|
||||
categories: categories_tree
|
||||
.iter()
|
||||
.map(|result| {
|
||||
result.map(|(k, v)| deserialize(&k).and_then(|k| deserialize(&v).map(|v| (k, v))))
|
||||
})
|
||||
.collect::<Result<Result<_, _>, _>>()??,
|
||||
};
|
||||
|
||||
Ok(Template::render("index", &context))
|
||||
}
|
||||
|
||||
#[get("/history")]
|
||||
pub fn history(db: State<'_, sled::Db>) -> Result<Template, StatusJson> {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HistoryEntryContext {
|
||||
category: categories::V,
|
||||
session: past_sessions::V,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct TemplateContext {
|
||||
entries: Vec<HistoryEntryContext>,
|
||||
}
|
||||
|
||||
let categories_tree = db.open_tree(categories::NAME)?;
|
||||
let past_sessions_tree = db.open_tree(past_sessions::NAME)?;
|
||||
|
||||
let categories: HashMap<categories::K, categories::V> = categories_tree
|
||||
.iter()
|
||||
.map(|result| {
|
||||
result.map(|(k, v)| deserialize(&k).and_then(|k| deserialize(&v).map(|v| (k, v))))
|
||||
})
|
||||
.collect::<Result<Result<_, _>, _>>()??;
|
||||
|
||||
let past_sessions: HashMap<past_sessions::K, past_sessions::V> = past_sessions_tree
|
||||
.iter()
|
||||
.map(|result| {
|
||||
result.map(|(k, v)| deserialize(&k).and_then(|k| deserialize(&v).map(|v| (k, v))))
|
||||
})
|
||||
.collect::<Result<Result<_, _>, _>>()??;
|
||||
|
||||
let mut context = dbg!(TemplateContext {
|
||||
entries: past_sessions
|
||||
.into_iter()
|
||||
.map(|(_, session)| {
|
||||
let category = categories.get(&session.category).unwrap().clone();
|
||||
HistoryEntryContext {
|
||||
duration: (session.ended - session.started).to_std().unwrap(),
|
||||
category,
|
||||
session,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
|
||||
// Newest entries first
|
||||
context.entries.sort_by_key(|entry| entry.session.started);
|
||||
context.entries.reverse();
|
||||
|
||||
Ok(Template::render("history", &context))
|
||||
}
|
||||
@ -59,7 +59,6 @@ impl<'r> Responder<'r> for StatusJson {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[duplicate(
|
||||
status_code T;
|
||||
[ Status::InternalServerError ] [ sled::Error ];
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
body {
|
||||
ody {
|
||||
font-family: Ubuntu;
|
||||
}
|
||||
|
||||
@ -6,14 +6,14 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
ul.category_list {
|
||||
ul.striped_list {
|
||||
max-width: 40em;
|
||||
list-style-type: none;
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.category_list > li:nth-of-type(odd) {
|
||||
ul.striped_list > li:nth-of-type(odd) {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
@ -80,23 +80,23 @@ ul.category_list > li:nth-of-type(odd) {
|
||||
border-width: 0px 0 0px 50px;
|
||||
}
|
||||
|
||||
|
||||
.history_list {
|
||||
|
||||
}
|
||||
|
||||
.history_entry {
|
||||
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
.history_entry_category {
|
||||
|
||||
}
|
||||
|
||||
.history_entry_started {
|
||||
.history_entry_duration {
|
||||
color: #892be1;
|
||||
}
|
||||
|
||||
.history_entry_started {
|
||||
color: #cc661e;
|
||||
}
|
||||
|
||||
.history_entry_ended {
|
||||
|
||||
color: #9f2727;
|
||||
}
|
||||
|
||||
|
||||
@ -14,16 +14,18 @@
|
||||
|
||||
<body>
|
||||
<h1 class="title">stl</h1>
|
||||
<div class="history_list">
|
||||
{{#each sessions}}
|
||||
<div class="history_entry">
|
||||
<span class="history_entry_category">{{this.0.name}}</span>
|
||||
<ul class="striped_list">
|
||||
{{#each entries}}
|
||||
<li class="history_entry">
|
||||
<span class="history_entry_category">{{this.category.name}}</span>
|
||||
<span> for </span>
|
||||
<span class="history_entry_duration">{{pretty_seconds this.duration.secs}}</span>
|
||||
<span> from </span>
|
||||
<span class="history_entry_started">{{pretty_datetime this.1.started}}</span>
|
||||
<span class="history_entry_started">{{pretty_datetime this.session.started}}</span>
|
||||
<span> to </span>
|
||||
<span class="history_entry_ended">{{pretty_datetime this.1.ended}}</span>
|
||||
</div>
|
||||
<span class="history_entry_ended">{{pretty_datetime this.session.ended}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</div>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
<form action="/toggle_category/{{this.0}}" method="post" id="{{this.0}}"></form>
|
||||
{{/each}}
|
||||
|
||||
<ul class="category_list">
|
||||
<ul class="striped_list">
|
||||
{{#each categories}}
|
||||
<li class="category_entry">
|
||||
<div class="category_icon"
|
||||
|
||||
Reference in New Issue
Block a user