Skip to content

Commit

Permalink
feat: add timezone support
Browse files Browse the repository at this point in the history
  • Loading branch information
ssaavedra committed Aug 26, 2024
1 parent f689a45 commit bc12dd0
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 46 deletions.
76 changes: 76 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ sqlx = { version = "=0.7.3", features = ["chrono", "macros", "migrate"], default
chrono = { version = "0.4.38", features = ["serde"] }
anyhow = "1.0.86"
poloto = "19.1.2"
chrono-tz = "0.9.0"
33 changes: 33 additions & 0 deletions src/form.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Custom form fields for Rocket
use std::str::FromStr;

use chrono::NaiveDateTime;
use chrono::TimeZone;

/// Custom form field to parse a datetime string
/// in the format of %Y-%m-%dT%H:%M:%S
Expand All @@ -27,6 +30,36 @@ impl<'r> rocket::form::FromFormField<'r> for ParseableDateTime {
impl core::ops::Deref for ParseableDateTime {
type Target = chrono::DateTime<chrono::Utc>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

#[derive(Default)]
pub struct Tz(pub chrono_tz::Tz);

impl<'r> rocket::form::FromFormField<'r> for Tz {
fn from_value(field: rocket::form::ValueField<'r>) -> rocket::form::Result<'r, Self> {
let value = field.value.to_string();
let tz = match value.as_str() {
// Try to parse as "Europe/Paris, America/New_York, etc"
s if s.contains('/') => {
chrono_tz::Tz::from_str(s).ok().unwrap_or(chrono_tz::Tz::UTC)
},
_ => chrono_tz::Tz::UTC,
};

Ok(Tz(tz))
}

fn default() -> Option<Self> {
Some(Tz(chrono_tz::Tz::UTC))
}
}

impl core::ops::Deref for Tz {
type Target = chrono_tz::Tz;

fn deref(&self) -> &Self::Target {
&self.0
}
Expand Down
21 changes: 15 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
//! - New fairings like the EVChargeFairing could be implmented in the future to
//! add add other IoT devices or additional functionality.
//!
use chrono::TimeZone;
use form::ParseableDateTime;
use governor::Quota;
use print_table::{get_avg_max_rows_for_token, get_paginated_rows_for_token, NoRowsError};
Expand Down Expand Up @@ -147,18 +148,19 @@ async fn post_token(
}

/// Route GET /log/:token/html will return the data in HTML format
#[get("/log/<_>/html?<page>&<count>", rank = 1)]
#[get("/log/<_>/html?<page>&<count>&<tz>", rank = 1)]
async fn list_table_html(
page: Option<i32>,
count: Option<i32>,
token: &ValidDbToken,
tz: form::Tz,
mut db: Connection<Logs>,
_ratelimit: RocketGovernor<'_, RateLimitGuard>,
) -> (ContentType, String) {
let page = page.unwrap_or(0);
let count = count.unwrap_or(10);

let (rows, has_next) = get_paginated_rows_for_token(&mut db, &token, page, count).await;
let (rows, has_next) = get_paginated_rows_for_token(&mut db, &token, page, count, &tz.0).await;

let mut result = String::new();
result.push_str("<!DOCTYPE html><html><head><meta charset=\"utf-8\"/><title>Consumption info</title></head><body><table>");
Expand All @@ -169,6 +171,7 @@ async fn list_table_html(
result.push_str(&row.to_html());
}
result.push_str("\n</table>\n");

if has_next {
result.push_str(&format!(
"<a href=\"/log/{}/html?page={}&count={}\">Next</a>",
Expand All @@ -177,24 +180,29 @@ async fn list_table_html(
count
));
}

// Add svg embedded
result.push_str(format!("<hr /><img src=\"/log/{}/svg?tz={}\" alt=\"Energy consumption\" />\n", token.full_token(), tz.0).as_str());

result.push_str("</body></html>\n");

(ContentType::HTML, result)
}

/// Route GET /log/:token/json will return the data in JSON format
#[get("/log/<_>/json?<page>&<count>", rank = 1)]
#[get("/log/<_>/json?<page>&<count>&<tz>", rank = 1)]
async fn list_table_json(
page: Option<i32>,
count: Option<i32>,
token: &ValidDbToken,
tz: form::Tz,
mut db: Connection<Logs>,
_ratelimit: RocketGovernor<'_, RateLimitGuard>,
) -> rocket::response::content::RawJson<String> {
let page = page.unwrap_or(0);
let count = count.unwrap_or(10);

let (rows, has_next) = get_paginated_rows_for_token(&mut db, &token, page, count).await;
let (rows, has_next) = get_paginated_rows_for_token(&mut db, &token, page, count, &tz.0).await;

let next_url = if has_next {
format!(
Expand All @@ -217,12 +225,13 @@ async fn list_table_json(


/// Route GET /log/:token/html will return the data in HTML format
#[get("/log/<_>/svg?<start>&<end>&<interval>", rank = 1)]
#[get("/log/<_>/svg?<start>&<end>&<interval>&<tz>", rank = 1)]
async fn list_table_svg(
start: Option<ParseableDateTime>,
end: Option<ParseableDateTime>,
interval: Option<i32>,
token: &ValidDbToken,
tz: form::Tz,
mut db: Connection<Logs>,
_ratelimit: RocketGovernor<'_, RateLimitGuard>,
) -> (ContentType, String) {
Expand All @@ -235,7 +244,7 @@ async fn list_table_svg(

let (avg, max) = get_avg_max_rows_for_token(&mut db, &token, &start, &end, interval).await;

match print_table::to_svg_plot(avg, max) {
match print_table::to_svg_plot(avg, max, &tz.0) {
Ok(svg) => (ContentType::SVG, svg),
Err(e) if e.downcast_ref::<NoRowsError>().is_some() => {
(ContentType::Plain, "No data found for the given request".to_string())
Expand Down
Loading

0 comments on commit bc12dd0

Please sign in to comment.