diff --git a/geneit_backend/Cargo.lock b/geneit_backend/Cargo.lock index 96f8ca9..ede325a 100644 --- a/geneit_backend/Cargo.lock +++ b/geneit_backend/Cargo.lock @@ -727,8 +727,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.52.5", ] @@ -1417,12 +1419,14 @@ dependencies = [ "anyhow", "base64 0.22.1", "bcrypt", + "chrono", "clap", "diesel", "diesel_migrations", "env_logger", "futures-util", "httpdate", + "ical", "image", "lazy_static", "lettre", @@ -1776,6 +1780,15 @@ dependencies = [ "cc", ] +[[package]] +name = "ical" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7cab7543a8b7729a19e2c04309f902861293dcdae6558dfbeb634454d279f6" +dependencies = [ + "thiserror", +] + [[package]] name = "ident_case" version = "1.0.1" diff --git a/geneit_backend/Cargo.toml b/geneit_backend/Cargo.toml index 13bafbf..7a4fc50 100644 --- a/geneit_backend/Cargo.toml +++ b/geneit_backend/Cargo.toml @@ -38,3 +38,5 @@ zip = "2.0.0" mime_guess = "2.0.4" tempfile = "3.10.1" base64 = "0.22.0" +ical = { version = "0.11.0", features = ["generator", "ical", "vcard"] } +chrono = "0.4.38" \ No newline at end of file diff --git a/geneit_backend/src/controllers/accommodations_reservations_calendars_controller.rs b/geneit_backend/src/controllers/accommodations_reservations_calendars_controller.rs index f5211ec..9eeacc0 100644 --- a/geneit_backend/src/controllers/accommodations_reservations_calendars_controller.rs +++ b/geneit_backend/src/controllers/accommodations_reservations_calendars_controller.rs @@ -1,10 +1,17 @@ +use ical::{generator::*, *}; + +use actix_web::{web, HttpResponse}; +use chrono::DateTime; + use crate::constants::StaticConstraints; use crate::controllers::HttpResult; use crate::extractors::accommodation_reservation_calendar_extractor::FamilyAndAccommodationReservationCalendarInPath; use crate::extractors::family_extractor::FamilyInPath; -use crate::models::AccommodationID; -use crate::services::{accommodations_list_service, accommodations_reservations_calendars_service}; -use actix_web::{web, HttpResponse}; +use crate::models::{AccommodationID, ReservationStatus}; +use crate::services::{ + accommodations_list_service, accommodations_reservations_calendars_service, + accommodations_reservations_service, families_service, +}; #[derive(serde::Deserialize)] pub struct CreateCalendarQuery { @@ -65,3 +72,86 @@ pub async fn delete(resa: FamilyAndAccommodationReservationCalendarInPath) -> Ht accommodations_reservations_calendars_service::delete(resa.to_reservation()).await?; Ok(HttpResponse::Ok().json("Calendar successfully deleted")) } + +fn fmt_date(time: i64) -> String { + let res = DateTime::from_timestamp(time, 0).expect("Failed to parse date"); + + /*format!( + "{:0>4}{:0>2}{:0>2}T{:0>2}{:0>2}", + res.year(), + res.month(), + res.day(), + res.minute(), + res.second() + )*/ + + res.format("%Y%m%dT%H%M%S").to_string() +} + +#[derive(serde::Deserialize)] +pub struct AnonymousAccessURL { + token: String, +} + +/// Get the content of the calendar +pub async fn anonymous_access(req: web::Path) -> HttpResult { + let calendar = + match accommodations_reservations_calendars_service::get_by_token(&req.token).await { + Ok(c) => c, + Err(e) => { + log::error!("Calendar information could not be retrieved: {e}"); + return Ok(HttpResponse::NotFound().body("Calendar not found!")); + } + }; + + let accommodations = + accommodations_list_service::get_all_of_family(calendar.family_id()).await?; + let members = families_service::get_memberships_of_family(calendar.family_id()).await?; + + // Get calendar associated events + let events = match calendar.accommodation_id() { + None => { + accommodations_reservations_service::get_all_of_family(calendar.family_id()).await? + } + Some(a) => accommodations_reservations_service::get_all_of_accommodation(a).await?, + }; + + let mut cal = IcalCalendarBuilder::version("2.0") + .gregorian() + .prodid("-//geneit//") + .build(); + + for ev in events { + let accommodation = accommodations + .iter() + .find(|a| a.id() == ev.accommodation_id()) + .unwrap(); + let member_name = members + .iter() + .find(|a| a.membership.user_id() == ev.user_id()) + .map(|m| m.user_name.as_str()) + .unwrap_or("other user"); + + let event = IcalEventBuilder::tzid("Europe/Paris") + .uid(format!("resa-{}", ev.id().0)) + .changed(fmt_date(ev.time_update)) + .start(fmt_date(ev.reservation_start)) + .end(fmt_date(ev.reservation_end)) + .set(ical_property!("SUMMARY", member_name)) + .set(ical_property!("LOCATION", &accommodation.name)) + .set(ical_property!( + "STATUS", + match ev.status() { + ReservationStatus::Pending => "TENTATIVE", + ReservationStatus::Accepted => "CONFIRMED", + ReservationStatus::Rejected => "CANCELLED", + } + )) + .build(); + cal.events.push(event); + } + + Ok(HttpResponse::Ok() + .content_type("text/calendar") + .body(cal.generate())) +} diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index 940ce31..ff815fc 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -270,7 +270,10 @@ async fn main() -> std::io::Result<()> { "/family/{id}/accommodations/reservations_calendars/{cal_id}", web::delete().to(accommodations_reservations_calendars_controller::delete), ) - // TODO : anonymous URL access + .route( + "/acccommodations_calendar/{token}", + web::get().to(accommodations_reservations_calendars_controller::anonymous_access), + ) // Photos controller .route( "/photo/{id}", diff --git a/geneit_backend/src/models.rs b/geneit_backend/src/models.rs index 80e9ec5..720a39d 100644 --- a/geneit_backend/src/models.rs +++ b/geneit_backend/src/models.rs @@ -485,6 +485,12 @@ pub struct NewAccommodation { #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)] pub struct AccommodationReservationID(pub i32); +pub enum ReservationStatus { + Pending, + Accepted, + Rejected, +} + #[derive(Queryable, Debug, serde::Serialize)] pub struct AccommodationReservation { id: i32, @@ -514,6 +520,14 @@ impl AccommodationReservation { pub fn user_id(&self) -> UserID { UserID(self.user_id) } + + pub fn status(&self) -> ReservationStatus { + match self.validated { + None => ReservationStatus::Pending, + Some(true) => ReservationStatus::Accepted, + Some(false) => ReservationStatus::Rejected, + } + } } #[derive(Insertable)] diff --git a/geneit_backend/src/services/accommodations_reservations_calendars_service.rs b/geneit_backend/src/services/accommodations_reservations_calendars_service.rs index 538bb3c..ff07470 100644 --- a/geneit_backend/src/services/accommodations_reservations_calendars_service.rs +++ b/geneit_backend/src/services/accommodations_reservations_calendars_service.rs @@ -61,6 +61,15 @@ pub async fn get_by_id( }) } +/// Get a single calendar by its token +pub async fn get_by_token(token: &str) -> anyhow::Result { + db_connection::execute(|conn| { + accommodations_reservations_cals_urls::table + .filter(accommodations_reservations_cals_urls::dsl::token.eq(token)) + .get_result(conn) + }) +} + /// Delete a calendar pub async fn delete(r: AccommodationReservationCalendar) -> anyhow::Result<()> { db_connection::execute(|conn| { diff --git a/geneit_backend/src/services/families_service.rs b/geneit_backend/src/services/families_service.rs index 6bfdccb..5cb47b5 100644 --- a/geneit_backend/src/services/families_service.rs +++ b/geneit_backend/src/services/families_service.rs @@ -129,9 +129,9 @@ pub async fn update_membership(membership: &Membership) -> anyhow::Result<()> { #[derive(serde::Serialize)] pub struct FamilyMember { #[serde(flatten)] - membership: Membership, - user_name: String, - user_mail: String, + pub membership: Membership, + pub user_name: String, + pub user_mail: String, } /// Get information about the users of a family