major features update
This commit is contained in:
parent
519fb49901
commit
4e2fab67c5
48 changed files with 3925 additions and 208 deletions
88
src/web/ics.rs
Normal file
88
src/web/ics.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
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"));
|
||||
let contacts: Vec<HydratedContact> = sqlx::query_as(
|
||||
"select id, birthday, (
|
||||
select string_agg(name,'\x1c' order by sort)
|
||||
from names where contact_id = c.id
|
||||
) as names
|
||||
from contacts c",
|
||||
)
|
||||
.fetch_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),
|
||||
yo_date.month,
|
||||
yo_date.day,
|
||||
) {
|
||||
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())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue