refactor: switch from chrono to jiff
This commit is contained in:
parent
79a054ab40
commit
3ffdf8f0d7
12 changed files with 205 additions and 161 deletions
|
|
@ -7,19 +7,19 @@ use axum::{
|
|||
};
|
||||
use axum_extra::extract::Form;
|
||||
use cache_bust::asset;
|
||||
use chrono::DateTime;
|
||||
use jiff::{Timestamp, Unit, tz::TimeZone};
|
||||
use maud::{Markup, html};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use slug::slugify;
|
||||
use sqlx::{QueryBuilder, Sqlite};
|
||||
use sqlx::QueryBuilder;
|
||||
|
||||
use super::Layout;
|
||||
use super::home::journal_section;
|
||||
use crate::db::DbId;
|
||||
use crate::models::user::AuthSession;
|
||||
use crate::models::{HydratedContact, JournalEntry};
|
||||
use crate::switchboard::{MentionHost, MentionHostType, insert_mentions, Switchboard};
|
||||
use crate::switchboard::{MentionHost, MentionHostType, Switchboard, insert_mentions};
|
||||
use crate::{AppError, AppState};
|
||||
|
||||
pub mod fields;
|
||||
|
|
@ -40,22 +40,22 @@ pub fn router() -> Router<AppState> {
|
|||
.route("/contact/{contact_id}/edit", get(self::get::contact_edit))
|
||||
}
|
||||
|
||||
fn human_delta(delta: &chrono::TimeDelta) -> String {
|
||||
if delta.num_days() == 0 {
|
||||
return "today".to_string();
|
||||
}
|
||||
fn human_delta(span: &jiff::Span) -> String {
|
||||
let todate = Timestamp::now().to_zoned(TimeZone::UTC).date();
|
||||
let span = span
|
||||
.round(
|
||||
jiff::SpanRound::new()
|
||||
.largest(Unit::Year)
|
||||
.smallest(Unit::Day)
|
||||
.relative(todate),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut result = "in ".to_string();
|
||||
let mut rem = delta.clone();
|
||||
if rem.num_days().abs() >= 7 {
|
||||
let weeks = rem.num_days() / 7;
|
||||
rem -= chrono::TimeDelta::days(weeks * 7);
|
||||
result.push_str(&format!("{}w ", weeks));
|
||||
if span.is_zero() {
|
||||
"today".to_string()
|
||||
} else {
|
||||
format!("in {:#}", span)
|
||||
}
|
||||
if rem.num_days().abs() > 0 {
|
||||
result.push_str(&format!("{}d ", rem.num_days()));
|
||||
}
|
||||
result.trim().to_string()
|
||||
}
|
||||
|
||||
mod get {
|
||||
|
|
@ -88,7 +88,9 @@ mod get {
|
|||
.await?;
|
||||
|
||||
let freshened = std::cmp::max(
|
||||
contact.manually_freshened_at.map(|when| when.date_naive()),
|
||||
contact
|
||||
.manually_freshened_at
|
||||
.map(|when| when.to_zoned(jiff::tz::TimeZone::UTC).date()),
|
||||
entries.get(0).map(|entry| entry.date),
|
||||
);
|
||||
|
||||
|
|
@ -213,7 +215,7 @@ mod get {
|
|||
let mfresh_str = contact
|
||||
.manually_freshened_at
|
||||
.clone()
|
||||
.map_or("".to_string(), |m| m.to_rfc3339());
|
||||
.map_or("".to_string(), |m| m.to_string());
|
||||
|
||||
let text_body: String =
|
||||
sqlx::query!("select text_body from contacts where id = $1", contact_id)
|
||||
|
|
@ -224,7 +226,7 @@ mod get {
|
|||
|
||||
Ok(layout.render(
|
||||
format!("Edit: {}", contact.names.get(0).unwrap_or(&String::from("(unknown)"))),
|
||||
Some(vec![asset!("contact.css")]),
|
||||
Some(vec![asset!("contact.css")]),
|
||||
html! {
|
||||
form hx-ext="response-targets" {
|
||||
div {
|
||||
|
|
@ -351,14 +353,15 @@ mod put {
|
|||
Some(payload.birthday)
|
||||
};
|
||||
|
||||
let manually_freshened_at = if payload.manually_freshened_at.is_empty() {
|
||||
let manually_freshened_at: Option<String> = if payload.manually_freshened_at.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
DateTime::parse_from_str(&payload.manually_freshened_at, "%+")
|
||||
payload
|
||||
.manually_freshened_at
|
||||
.parse::<Timestamp>()
|
||||
.map_err(|_| anyhow::Error::msg("Could not parse freshened-at string"))?
|
||||
.to_utc()
|
||||
.to_rfc3339(),
|
||||
.to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -386,9 +389,6 @@ mod put {
|
|||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if old_contact.text_body != text_body {
|
||||
}
|
||||
|
||||
// these blocks are not in functions because payload gets progressively
|
||||
// partially moved as we handle each field and i don't want to deal with it
|
||||
|
||||
|
|
@ -488,25 +488,21 @@ mod put {
|
|||
let old_names: Vec<String> = old_names.into_iter().map(|(s,)| s).collect();
|
||||
|
||||
if old_names != new_names {
|
||||
sqlx::query!(
|
||||
"delete from names where contact_id = $1",
|
||||
contact_id
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if !new_names.is_empty() {
|
||||
QueryBuilder::new(
|
||||
"insert into names (contact_id, sort, name) "
|
||||
).push_values(new_names.iter().enumerate(), |mut b, (sort, name)| {
|
||||
b
|
||||
.push_bind(contact_id)
|
||||
.push_bind(DbId::try_from(sort).unwrap())
|
||||
.push_bind(name);
|
||||
}).build()
|
||||
.persistent(false)
|
||||
sqlx::query!("delete from names where contact_id = $1", contact_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if !new_names.is_empty() {
|
||||
QueryBuilder::new("insert into names (contact_id, sort, name) ")
|
||||
.push_values(new_names.iter().enumerate(), |mut b, (sort, name)| {
|
||||
b.push_bind(contact_id)
|
||||
.push_bind(DbId::try_from(sort).unwrap())
|
||||
.push_bind(name);
|
||||
})
|
||||
.build()
|
||||
.persistent(false)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -524,12 +520,9 @@ mod put {
|
|||
let old_groups: Vec<String> = old_groups.into_iter().map(|(s,)| s).collect();
|
||||
|
||||
if new_groups != old_groups {
|
||||
sqlx::query!(
|
||||
"delete from groups where contact_id = $1",
|
||||
contact_id
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query!("delete from groups where contact_id = $1", contact_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if new_groups.len() > 0 {
|
||||
QueryBuilder::new("insert into groups (contact_id, name, slug) ")
|
||||
|
|
@ -566,7 +559,6 @@ mod put {
|
|||
.await?;
|
||||
}
|
||||
|
||||
|
||||
if regen_text_body {
|
||||
sqlx::query!(
|
||||
"delete from mentions where entity_id = $1 and entity_type = $2",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use cache_bust::asset;
|
||||
use chrono::{Local, NaiveDate, TimeDelta};
|
||||
use jiff::{Timestamp, Unit, Zoned, civil, tz::TimeZone};
|
||||
use maud::{Markup, html};
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ use crate::{AppError, AppState};
|
|||
struct ContactFreshness {
|
||||
contact_id: DbId,
|
||||
display: String,
|
||||
fresh_date: NaiveDate,
|
||||
fresh_date: civil::Date,
|
||||
fresh_str: String,
|
||||
elapsed_str: String,
|
||||
}
|
||||
|
|
@ -46,8 +46,8 @@ fn freshness_section(freshens: &Vec<ContactFreshness>) -> Result<Markup, AppErro
|
|||
struct KnownBirthdayContact {
|
||||
contact_id: i64,
|
||||
display: String,
|
||||
prev_birthday: NaiveDate,
|
||||
next_birthday: NaiveDate,
|
||||
prev_birthday: civil::Date,
|
||||
next_birthday: civil::Date,
|
||||
}
|
||||
fn birthdays_section(
|
||||
prev_birthdays: &Vec<KnownBirthdayContact>,
|
||||
|
|
@ -64,7 +64,7 @@ fn birthdays_section(
|
|||
(contact.display)
|
||||
}
|
||||
span {
|
||||
(contact.next_birthday.format("%m-%d"))
|
||||
(contact.next_birthday.strftime("%m-%d"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ fn birthdays_section(
|
|||
(contact.display)
|
||||
}
|
||||
span {
|
||||
(contact.prev_birthday.format("%m-%d"))
|
||||
(contact.prev_birthday.strftime("%m-%d"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ pub async fn journal_section(
|
|||
added to the top of the list regardless of date; refresh the page to re-sort."
|
||||
}
|
||||
form hx-post="/journal_entry" hx-target="next .entries" hx-target-error="#journal-error" hx-swap="afterbegin" hx-on::after-request="if(event.detail.successful) this.reset()" {
|
||||
input name="date" placeholder=(Local::now().date_naive().to_string());
|
||||
input name="date" placeholder=(Zoned::now().date().to_string());
|
||||
textarea name="value" placeholder="New entry..." autofocus {}
|
||||
input type="submit" value="Add Entry";
|
||||
}
|
||||
|
|
@ -135,11 +135,11 @@ pub mod get {
|
|||
.clone()
|
||||
.into_iter()
|
||||
.map(|contact| {
|
||||
let zero = NaiveDate::from_epoch_days(0).unwrap();
|
||||
let zero = jiff::civil::Date::ZERO;
|
||||
let fresh_date = std::cmp::max(
|
||||
contact
|
||||
.manually_freshened_at
|
||||
.map(|x| x.date_naive())
|
||||
.map(|ts| ts.to_zoned(TimeZone::UTC).date())
|
||||
.unwrap_or(zero),
|
||||
contact.last_mention_date.unwrap_or(zero),
|
||||
);
|
||||
|
|
@ -152,30 +152,23 @@ pub mod get {
|
|||
elapsed_str: "".to_string(),
|
||||
}
|
||||
} else {
|
||||
let mut duration = Local::now().date_naive().signed_duration_since(fresh_date);
|
||||
let mut elapsed: Vec<String> = Vec::new();
|
||||
let y = duration.num_weeks() / 52;
|
||||
let count = |n: i64, noun: &str| {
|
||||
format!("{} {}{}", n, noun, if n > 1 { "s" } else { "" })
|
||||
};
|
||||
if y > 0 {
|
||||
elapsed.push(count(y, "year"));
|
||||
duration -= TimeDelta::weeks(y * 52);
|
||||
}
|
||||
let w = duration.num_weeks();
|
||||
if w > 0 {
|
||||
elapsed.push(count(w, "week"));
|
||||
duration -= TimeDelta::weeks(w);
|
||||
}
|
||||
let d = duration.num_days();
|
||||
if d > 0 {
|
||||
elapsed.push(count(d, "day"));
|
||||
}
|
||||
let utc = TimeZone::UTC;
|
||||
let todate = Timestamp::now().to_zoned(utc.clone()).date();
|
||||
let duration = todate
|
||||
.since(&fresh_date.to_zoned(utc).unwrap())
|
||||
.unwrap()
|
||||
.round(
|
||||
jiff::SpanRound::new()
|
||||
.largest(Unit::Year)
|
||||
.smallest(Unit::Day)
|
||||
.relative(todate),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let elapsed_str = if elapsed.is_empty() {
|
||||
let elapsed_str = if duration.is_zero() {
|
||||
"today".to_string()
|
||||
} else {
|
||||
elapsed.join(", ")
|
||||
format!("{:#}", duration)
|
||||
};
|
||||
|
||||
ContactFreshness {
|
||||
|
|
@ -197,8 +190,8 @@ pub mod get {
|
|||
Some(KnownBirthdayContact {
|
||||
contact_id: contact.id,
|
||||
display: contact.display_name(),
|
||||
prev_birthday: date.prev_month_day_occurrence().unwrap(),
|
||||
next_birthday: date.next_month_day_occurrence().unwrap(),
|
||||
prev_birthday: date.prev_month_day_occurrence()?,
|
||||
next_birthday: date.next_month_day_occurrence()?,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ mod get {
|
|||
for contact in &contacts {
|
||||
if let Some(Birthday::Date(yo_date)) = &contact.birthday {
|
||||
if let Some(date) = NaiveDate::from_ymd_opt(
|
||||
yo_date.year.unwrap_or(1900),
|
||||
yo_date.month,
|
||||
yo_date.day,
|
||||
yo_date.year.unwrap_or(1900).into(),
|
||||
yo_date.month.try_into().unwrap(),
|
||||
yo_date.day.try_into().unwrap(),
|
||||
) {
|
||||
calendar.push(
|
||||
Event::new()
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ use axum::{
|
|||
response::IntoResponse,
|
||||
routing::{delete, patch, post},
|
||||
};
|
||||
use chrono::{Datelike, Local, NaiveDate};
|
||||
use jiff::{Zoned, civil::Date};
|
||||
use maud::Markup;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::models::JournalEntry;
|
||||
use crate::models::user::AuthSession;
|
||||
use crate::switchboard::{MentionHost, MentionHostType, insert_mentions};
|
||||
use crate::switchboard::{MentionHostType, insert_mentions};
|
||||
use crate::{AppError, AppState, DbId};
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
|
|
@ -39,10 +39,10 @@ mod post {
|
|||
let pool = &state.db(&user).pool;
|
||||
let sw_lock = state.switchboard(&user);
|
||||
|
||||
let now = Local::now().date_naive();
|
||||
let now = Zoned::now();
|
||||
|
||||
let date = if payload.date.is_empty() {
|
||||
now
|
||||
now.date()
|
||||
} else {
|
||||
let date_re =
|
||||
Regex::new(r"^(?:(?<year>[0-9]{4})-)?(?:(?<month>[0-9]{2})-)?(?<day>[0-9]{2})$")
|
||||
|
|
@ -54,17 +54,16 @@ mod post {
|
|||
// unwrapping these parses is safe since it's matching [0-9]{2,4}
|
||||
let year = caps
|
||||
.name("year")
|
||||
.map(|m| m.as_str().parse::<i32>().unwrap())
|
||||
.map(|m| m.as_str().parse::<i16>().unwrap())
|
||||
.unwrap_or(now.year());
|
||||
let month = caps
|
||||
.name("month")
|
||||
.map(|m| m.as_str().parse::<u32>().unwrap())
|
||||
.map(|m| m.as_str().parse::<i8>().unwrap())
|
||||
.unwrap_or(now.month());
|
||||
let day = caps.name("day").unwrap().as_str().parse::<u32>().unwrap();
|
||||
let day = caps.name("day").unwrap().as_str().parse::<i8>().unwrap();
|
||||
|
||||
NaiveDate::from_ymd_opt(year, month, day).ok_or(anyhow::Error::msg(
|
||||
"invalid date: failed NaiveDate construction",
|
||||
))?
|
||||
Date::new(year, month, day)
|
||||
.map_err(|_| anyhow::Error::msg("invalid date: failed NaiveDate construction"))?
|
||||
};
|
||||
|
||||
// not a macro query, we want to use JournalEntry's custom FromRow
|
||||
|
|
@ -131,7 +130,6 @@ mod patch {
|
|||
insert_mentions(&mentions, pool).await?;
|
||||
}
|
||||
|
||||
|
||||
Ok(new_entry.to_html(pool).await?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue