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 { Router::new().route("/cal/{path}", get(self::get::calendar)) } mod get { use super::*; pub async fn calendar( auth_session: AuthSession, Path(ics_path): Path, ) -> Result { let path_re = Regex::new(r"^(?.+)-(?[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 = 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,) = 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()) } }