From 74342dcc82d2aef47d4c965749c5670eb6ffaa36 Mon Sep 17 00:00:00 2001 From: Wes Holland Date: Thu, 28 Nov 2024 16:51:29 -0600 Subject: [PATCH] Add timezone config to user --- migrations/20241107225934_initial.sql | 1 + src/app/history.rs | 13 +++++++++-- src/auth.rs | 1 + src/db/bootstrap_data.rs | 11 ++++++++-- src/db/user.rs | 31 +++++++++++++++------------ src/main.rs | 1 + src/session.rs | 1 + src/util/mod.rs | 1 + src/util/time.rs | 19 ++++++++++++++++ templates/history.html | 2 ++ 10 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 src/util/mod.rs create mode 100644 src/util/time.rs diff --git a/migrations/20241107225934_initial.sql b/migrations/20241107225934_initial.sql index a2135d5..2bb9f17 100644 --- a/migrations/20241107225934_initial.sql +++ b/migrations/20241107225934_initial.sql @@ -4,6 +4,7 @@ CREATE TABLE IF NOT EXISTS User ( id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, role INTEGER NOT NULL, + tz_offset INTEGER NOT NULL, FOREIGN KEY(role) REFERENCES UserRole(id) ); diff --git a/src/app/history.rs b/src/app/history.rs index a1e7127..4912ddc 100644 --- a/src/app/history.rs +++ b/src/app/history.rs @@ -8,6 +8,8 @@ use tracing::info; use chrono::prelude::*; use crate::db::positive_adjustment::{get_positive_adjustments_target_date_range, DbPositiveAdjustment}; use crate::error::{AppError, QueryExtractor}; +use crate::session::SessionUser; +use crate::util::time::tz_offset_to_string; #[derive(Template)] #[template(path = "history.html")] @@ -17,6 +19,7 @@ struct HistoryLogTemplate { start_time: String, end_date: String, end_time: String, + time_zone: String, } #[derive(Template)] @@ -36,12 +39,15 @@ pub struct DatetimeRangeQueryArgs { pub end_date: Option, #[serde(rename = "end-time", alias = "et")] pub end_time: Option, + #[serde(rename = "timezone-offset", alias = "tz")] + pub time_zone_offset: Option, } pub async fn history_log_handler( QueryExtractor(query): QueryExtractor, HxRequest(hx_request): HxRequest, - State(db): State + State(db): State, + user: SessionUser ) -> Result { let today = Local::now().naive_local().date(); @@ -50,7 +56,8 @@ pub async fn history_log_handler( let start_time = query.start_time.unwrap_or("00:00:00".to_string()); let end_date = query.end_date.unwrap_or(today.to_string()); let end_time = query.end_time.unwrap_or("11:59:59".to_string()); - let timezone = FixedOffset::west_opt(6 * 3600) + let tz_offset = query.time_zone_offset.unwrap_or(user.tz_offset); + let timezone = FixedOffset::east_opt(tz_offset) .ok_or(anyhow::anyhow!("Invalid timezone"))?; let naive_start_date = start_date.parse::()?; @@ -84,12 +91,14 @@ pub async fn history_log_handler( Ok(HistoryLogItemFragmentTemplate {items}.into_response()) } else { + let time_zone = tz_offset_to_string(tz_offset); Ok(HistoryLogTemplate { items, start_date, start_time, end_date, end_time, + time_zone, }.into_response()) } } diff --git a/src/auth.rs b/src/auth.rs index cfbd041..6e74dbf 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -151,6 +151,7 @@ pub async fn auth_authorized( name: oauth_user_data.name, verified_email: oauth_user_data.verified_email, picture: oauth_user_data.picture, + tz_offset: db_user.tz_offset as i32, }; // STEP 10 - Save user session data diff --git a/src/db/bootstrap_data.rs b/src/db/bootstrap_data.rs index c4e7954..95af225 100644 --- a/src/db/bootstrap_data.rs +++ b/src/db/bootstrap_data.rs @@ -1,9 +1,12 @@ +use std::str::FromStr; use crate::db::user::{add_user, get_user_by_name, get_user_count, DbUserRole}; use anyhow::{anyhow, Result}; use serde::Deserialize; use sqlx::SqlitePool; use tracing::info; +const DEFAULT_TIMEZONE: i64 = -6 * 3600; + #[derive(Debug, Deserialize)] struct BootstrapData { users: Vec, @@ -12,7 +15,8 @@ struct BootstrapData { #[derive(Debug, Deserialize)] struct BootstrapUser { name: String, - role: String + role: String, + tz_offset: Option, } pub async fn bootstrap_database(db: &SqlitePool) -> Result<()> { @@ -33,8 +37,11 @@ pub async fn bootstrap_database(db: &SqlitePool) -> Result<()> { if db_needs_users(db).await? { for user in &data.users { let role = DbUserRole::try_from_str(&user.role).ok_or(anyhow!("invalid role {}", user.role))?; + let tz = if let Some(tz) = &user.tz_offset { + i64::from_str(&tz).unwrap_or(DEFAULT_TIMEZONE) + } else { DEFAULT_TIMEZONE }; if get_user_by_name(db, &user.name).await?.is_none() { - let new_id = add_user(db, &user.name, role).await?; + let new_id = add_user(db, &user.name, role, tz).await?; info!("bootstrap new user {}:{} ({})", new_id, user.name, user.role); } } diff --git a/src/db/user.rs b/src/db/user.rs index a15d781..6ae9801 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -8,35 +8,38 @@ pub struct DbUser { pub id: i64, pub name: String, pub role: i64, + pub tz_offset: i64, } pub async fn get_user_by_name(db: &SqlitePool, user: &str) -> Result> { - sqlx::query_as!( - DbUser, + sqlx::query_as::<_, DbUser>( r#" SELECT id, name, - role + role, + tz_offset FROM User WHERE name = ? - "#, - user - ).fetch_optional(db).await - .map_err(From::from) + "#) + .bind(user) + .fetch_optional(db).await + .map_err(From::from) } -pub async fn add_user(db: &SqlitePool, name: &str, role: DbUserRole) -> Result { +pub async fn add_user(db: &SqlitePool, name: &str, role: DbUserRole, tz_offset: i64) -> Result { let role = role.value(); - let res = sqlx::query!( + let res = sqlx::query( r#" - INSERT INTO User(name, role) - VALUES (?, ?) - "#, - name, role - ).execute(db).await?; + INSERT INTO User(name, role, tz_offset) + VALUES (?, ?, ?) + "#) + .bind(name) + .bind(role) + .bind(tz_offset) + .execute(db).await?; let new_id = res.last_insert_rowid(); diff --git a/src/main.rs b/src/main.rs index 05faf71..058cd06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ mod auth; mod static_routes; mod app; mod ingest; +mod util; //NOTE TO FUTURE ME: I'm leaving a bunch of notes about these things as part of the learning // process. There is a lot of implementation details that are obscured by all these pieces, and it // can be hard to tell how heavy a line is. Lots of comment in this file and some of the kind of diff --git a/src/session.rs b/src/session.rs index ba44933..6fc9b13 100644 --- a/src/session.rs +++ b/src/session.rs @@ -63,6 +63,7 @@ pub struct SessionUser { pub name: String, pub verified_email: bool, pub picture: String, + pub tz_offset: i32, } /// A custom error for the User extractor diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..c25ca52 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1 @@ +pub mod time; \ No newline at end of file diff --git a/src/util/time.rs b/src/util/time.rs new file mode 100644 index 0000000..6c4a27e --- /dev/null +++ b/src/util/time.rs @@ -0,0 +1,19 @@ + + +/// Super naive implementation. Just a quick and dirty to get a string +pub fn tz_offset_to_string(tz_offset_seconds: i32) -> String { + if tz_offset_seconds == 3600 * -6 { + return "Central".to_string(); + } + else if tz_offset_seconds == 3600 * -5 { + return "Eastern".to_string(); + } + else if tz_offset_seconds == 3600 * -7 { + return "Pacific".to_string(); + } + else if tz_offset_seconds < 0 { + return format!("UTC{}", tz_offset_seconds).to_string(); + } + + format!("UTC+{}", tz_offset_seconds).to_string() +} \ No newline at end of file diff --git a/templates/history.html b/templates/history.html index 1bc141e..92e77ba 100644 --- a/templates/history.html +++ b/templates/history.html @@ -15,6 +15,7 @@
+ Timezone {{ time_zone }}
@@ -26,6 +27,7 @@
+ Timezone {{ time_zone }}