Allow granular per-chat reminder configuration
This commit is contained in:
159
src/db.rs
159
src/db.rs
@@ -1,12 +1,20 @@
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::{DateTime, Duration, NaiveTime, TimeZone, Utc};
|
||||
use diesel::{
|
||||
associations::{Associations, Identifiable},
|
||||
prelude::Insertable,
|
||||
Queryable, Selectable,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::appointment::Appointment;
|
||||
use crate::schema::{chats, reminders};
|
||||
|
||||
const TIME_FORMAT: &str = "%H:%M";
|
||||
|
||||
#[derive(Queryable, Selectable, Identifiable)]
|
||||
#[diesel(table_name = chats)]
|
||||
pub struct DbChat {
|
||||
@@ -26,7 +34,135 @@ pub struct DbChat {
|
||||
pub struct DbReminder {
|
||||
id: i32,
|
||||
chat_id: i32,
|
||||
days_ahead: i64,
|
||||
reminder_delta_hours: i64,
|
||||
reminder_time: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = reminders)]
|
||||
pub struct NewDbReminder {
|
||||
chat_id: i32,
|
||||
reminder_delta_hours: i64,
|
||||
reminder_time: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Reminder {
|
||||
pub delta: Duration,
|
||||
pub time: Option<NaiveTime>,
|
||||
}
|
||||
|
||||
impl Reminder {
|
||||
pub fn to_localized_str<S: AsRef<str>>(&self, locale: S) -> String {
|
||||
let delta_mode;
|
||||
let delta;
|
||||
if self.delta.num_hours() % 24 == 0 {
|
||||
delta_mode = "days";
|
||||
delta = self.delta.num_days();
|
||||
} else {
|
||||
delta_mode = "hours";
|
||||
delta = self.delta.num_hours();
|
||||
};
|
||||
let pluralization = if delta == 1 { "one" } else { "other" };
|
||||
let delta_string = t!(
|
||||
&format!("reminders.{}.{}", delta_mode, pluralization),
|
||||
locale = locale.as_ref(),
|
||||
delta = delta,
|
||||
);
|
||||
let message_key = if self.delta.num_hours() > 0 {
|
||||
"reminders.delta"
|
||||
} else {
|
||||
"reminders.thisday_delta"
|
||||
};
|
||||
let mut result = t!(
|
||||
message_key,
|
||||
locale = locale.as_ref(),
|
||||
delta_text = &delta_string,
|
||||
);
|
||||
if let Some(time) = self.time {
|
||||
result += &t!(
|
||||
"reminders.time",
|
||||
locale = locale.as_ref(),
|
||||
time = time.format(TIME_FORMAT),
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn to_db(&self, chat_id: i32) -> NewDbReminder {
|
||||
let reminder_time = self.time.map(|time| time.format(TIME_FORMAT).to_string());
|
||||
let reminder_delta_hours = self.delta.num_hours();
|
||||
NewDbReminder {
|
||||
chat_id,
|
||||
reminder_delta_hours,
|
||||
reminder_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RE_REMINDER: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^(\d+)([hHdD])(?:@(\d{1,2})(?:[:](\d{2}))?)?$").unwrap());
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ParseReminderError {
|
||||
#[error("The reminder format is invalid")]
|
||||
InvalidReminderFormat,
|
||||
#[error("The time is invalid")]
|
||||
InvalidTime,
|
||||
#[error("The delta is not allowed to be zero")]
|
||||
DeltaZero,
|
||||
}
|
||||
|
||||
impl FromStr for Reminder {
|
||||
type Err = ParseReminderError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// TODO Better error messages
|
||||
let captures = RE_REMINDER
|
||||
.captures(s)
|
||||
.ok_or(ParseReminderError::InvalidReminderFormat)?;
|
||||
let mut delta = captures.get(1).unwrap().as_str().parse().unwrap();
|
||||
if captures.get(2).unwrap().as_str().to_lowercase() == "d" {
|
||||
delta *= 24;
|
||||
} else {
|
||||
// Setting a fixed time is not allowed in hour-mode
|
||||
if captures.get(3).is_some() {
|
||||
return Err(ParseReminderError::InvalidReminderFormat);
|
||||
}
|
||||
}
|
||||
let time = captures
|
||||
.get(3)
|
||||
.map(|capture| {
|
||||
let hours = capture.as_str().parse().unwrap();
|
||||
let minutes = captures
|
||||
.get(4)
|
||||
.map(|minutes| minutes.as_str().parse().unwrap())
|
||||
.unwrap_or(0);
|
||||
NaiveTime::from_hms_opt(hours, minutes, 0).ok_or(ParseReminderError::InvalidTime)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
if delta == 0 && time.is_none() {
|
||||
return Err(ParseReminderError::DeltaZero);
|
||||
}
|
||||
|
||||
let delta = Duration::hours(delta);
|
||||
Ok(Self { delta, time })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DbReminder> for Reminder {
|
||||
type Error = chrono::ParseError;
|
||||
|
||||
fn try_from(db_reminder: DbReminder) -> Result<Self, Self::Error> {
|
||||
let time = db_reminder
|
||||
.reminder_time
|
||||
.as_ref()
|
||||
.map(|s| NaiveTime::parse_from_str(s, TIME_FORMAT))
|
||||
.transpose()?;
|
||||
let delta = Duration::hours(db_reminder.reminder_delta_hours);
|
||||
|
||||
Ok(Self { delta, time })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChatInfo<Tz: TimeZone> {
|
||||
@@ -37,11 +173,14 @@ pub struct ChatInfo<Tz: TimeZone> {
|
||||
pub last_reminder: Option<DateTime<Tz>>,
|
||||
pub pinned_message_id: Option<i32>,
|
||||
pub locale: String,
|
||||
pub remind_days_ahead: Vec<u64>,
|
||||
pub reminders: Vec<Reminder>,
|
||||
}
|
||||
|
||||
impl ChatInfo<Utc> {
|
||||
pub fn from_db(db_chat: DbChat, db_reminders: Vec<DbReminder>) -> Self {
|
||||
pub fn from_db(
|
||||
db_chat: DbChat,
|
||||
db_reminders: Vec<DbReminder>,
|
||||
) -> Result<Self, chrono::ParseError> {
|
||||
let next_appointment = db_chat
|
||||
.next_appointment_start
|
||||
// Join appointments into single option
|
||||
@@ -57,12 +196,12 @@ impl ChatInfo<Utc> {
|
||||
|
||||
let locale = db_chat.locale.unwrap_or("de".into());
|
||||
|
||||
let remind_days_ahead = db_reminders
|
||||
let reminders = db_reminders
|
||||
.into_iter()
|
||||
.map(|reminder| reminder.days_ahead.try_into().unwrap_or(0))
|
||||
.collect();
|
||||
.map(Reminder::try_from)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
ChatInfo {
|
||||
Ok(ChatInfo {
|
||||
db_id: db_chat.id,
|
||||
id: db_chat.telegram_id,
|
||||
calendar: db_chat.calendar,
|
||||
@@ -70,7 +209,7 @@ impl ChatInfo<Utc> {
|
||||
last_reminder,
|
||||
pinned_message_id: db_chat.pinned_message_id,
|
||||
locale,
|
||||
remind_days_ahead,
|
||||
}
|
||||
reminders,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user