82 lines
3 KiB
Rust
82 lines
3 KiB
Rust
use axum::{Router, extract::Path, response::IntoResponse, routing::get};
|
|
use chrono::NaiveDate;
|
|
use icalendar::{Calendar, Component, Event, EventLike};
|
|
use regex::Regex;
|
|
|
|
use crate::models::user::{AuthSession, User};
|
|
use crate::models::{Birthday, HydratedContact};
|
|
use crate::{AppError, AppState, Database};
|
|
|
|
pub fn router() -> Router<AppState> {
|
|
Router::new().route("/cal/{path}", get(self::get::calendar))
|
|
}
|
|
|
|
mod get {
|
|
use super::*;
|
|
|
|
pub async fn calendar(
|
|
auth_session: AuthSession,
|
|
Path(ics_path): Path<String>,
|
|
) -> Result<impl IntoResponse, AppError> {
|
|
let path_re = Regex::new(r"^(?<username>.+)-(?<hash>[0-9a-zA-Z]+).ics$").unwrap();
|
|
|
|
let username = if let Some(caps) = path_re.captures(&ics_path) {
|
|
caps.name("username").unwrap().as_str()
|
|
} else {
|
|
tracing::debug!(
|
|
"No username match in path {:?} for re /^.+-[0-9a-zA-Z]+.ics$/",
|
|
ics_path
|
|
);
|
|
return Err(AppError(anyhow::Error::msg("TODO: 404")));
|
|
};
|
|
|
|
let user: Option<User> = auth_session.backend.find_user(username).await?;
|
|
if user.is_none() {
|
|
tracing::debug!("No matching user for username {:?}", username);
|
|
return Err(AppError(anyhow::Error::msg("TODO: 404")));
|
|
}
|
|
|
|
let user = user.unwrap();
|
|
let pool = Database::for_user(&user).await?.pool;
|
|
let expected_path: (Option<String>,) = sqlx::query_as("select ics_path from settings")
|
|
.fetch_one(&pool)
|
|
.await?;
|
|
let debug_ics_path = ics_path.clone();
|
|
if expected_path.0 != Some(ics_path) {
|
|
tracing::debug!(
|
|
"Expected path {:?} did not match request path {:?}",
|
|
expected_path.0,
|
|
debug_ics_path
|
|
);
|
|
return Err(AppError(anyhow::Error::msg("TODO: 404")));
|
|
}
|
|
|
|
let calname = format!("Contact birthdays for {}", user.username);
|
|
let mut calendar = Calendar::new();
|
|
calendar.name(&calname);
|
|
calendar.append_property(("PRODID", "Mascarpone CRM"));
|
|
|
|
// TODO; this does some db work to pull in last_modified_date that we don't use
|
|
let contacts = HydratedContact::all(&pool).await?;
|
|
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).into(),
|
|
yo_date.month.try_into().unwrap(),
|
|
yo_date.day.try_into().unwrap(),
|
|
) {
|
|
calendar.push(
|
|
Event::new()
|
|
.starts(date) // start-with-no-end is "all day"
|
|
.summary(&format!("{}'s Birthday", &contact.display_name()))
|
|
.add_property("RRULE", "FREQ=YEARLY"),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
tracing::debug!("{}", calendar);
|
|
|
|
Ok(calendar.to_string())
|
|
}
|
|
}
|