Add an accommodations reservations module #188

Merged
pierre merged 81 commits from accomodation_module into master 2024-06-22 21:30:26 +00:00
7 changed files with 138 additions and 7 deletions
Showing only changes of commit df6a9e8292 - Show all commits

View File

@ -727,8 +727,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen",
"windows-targets 0.52.5", "windows-targets 0.52.5",
] ]
@ -1417,12 +1419,14 @@ dependencies = [
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
"bcrypt", "bcrypt",
"chrono",
"clap", "clap",
"diesel", "diesel",
"diesel_migrations", "diesel_migrations",
"env_logger", "env_logger",
"futures-util", "futures-util",
"httpdate", "httpdate",
"ical",
"image", "image",
"lazy_static", "lazy_static",
"lettre", "lettre",
@ -1776,6 +1780,15 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "ical"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7cab7543a8b7729a19e2c04309f902861293dcdae6558dfbeb634454d279f6"
dependencies = [
"thiserror",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"

View File

@ -38,3 +38,5 @@ zip = "2.0.0"
mime_guess = "2.0.4" mime_guess = "2.0.4"
tempfile = "3.10.1" tempfile = "3.10.1"
base64 = "0.22.0" base64 = "0.22.0"
ical = { version = "0.11.0", features = ["generator", "ical", "vcard"] }
chrono = "0.4.38"

View File

@ -1,10 +1,17 @@
use ical::{generator::*, *};
use actix_web::{web, HttpResponse};
use chrono::DateTime;
use crate::constants::StaticConstraints; use crate::constants::StaticConstraints;
use crate::controllers::HttpResult; use crate::controllers::HttpResult;
use crate::extractors::accommodation_reservation_calendar_extractor::FamilyAndAccommodationReservationCalendarInPath; use crate::extractors::accommodation_reservation_calendar_extractor::FamilyAndAccommodationReservationCalendarInPath;
use crate::extractors::family_extractor::FamilyInPath; use crate::extractors::family_extractor::FamilyInPath;
use crate::models::AccommodationID; use crate::models::{AccommodationID, ReservationStatus};
use crate::services::{accommodations_list_service, accommodations_reservations_calendars_service}; use crate::services::{
use actix_web::{web, HttpResponse}; accommodations_list_service, accommodations_reservations_calendars_service,
accommodations_reservations_service, families_service,
};
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct CreateCalendarQuery { pub struct CreateCalendarQuery {
@ -65,3 +72,86 @@ pub async fn delete(resa: FamilyAndAccommodationReservationCalendarInPath) -> Ht
accommodations_reservations_calendars_service::delete(resa.to_reservation()).await?; accommodations_reservations_calendars_service::delete(resa.to_reservation()).await?;
Ok(HttpResponse::Ok().json("Calendar successfully deleted")) 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<AnonymousAccessURL>) -> 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()))
}

View File

@ -270,7 +270,10 @@ async fn main() -> std::io::Result<()> {
"/family/{id}/accommodations/reservations_calendars/{cal_id}", "/family/{id}/accommodations/reservations_calendars/{cal_id}",
web::delete().to(accommodations_reservations_calendars_controller::delete), 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 // Photos controller
.route( .route(
"/photo/{id}", "/photo/{id}",

View File

@ -485,6 +485,12 @@ pub struct NewAccommodation {
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct AccommodationReservationID(pub i32); pub struct AccommodationReservationID(pub i32);
pub enum ReservationStatus {
Pending,
Accepted,
Rejected,
}
#[derive(Queryable, Debug, serde::Serialize)] #[derive(Queryable, Debug, serde::Serialize)]
pub struct AccommodationReservation { pub struct AccommodationReservation {
id: i32, id: i32,
@ -514,6 +520,14 @@ impl AccommodationReservation {
pub fn user_id(&self) -> UserID { pub fn user_id(&self) -> UserID {
UserID(self.user_id) 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)] #[derive(Insertable)]

View File

@ -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<AccommodationReservationCalendar> {
db_connection::execute(|conn| {
accommodations_reservations_cals_urls::table
.filter(accommodations_reservations_cals_urls::dsl::token.eq(token))
.get_result(conn)
})
}
/// Delete a calendar /// Delete a calendar
pub async fn delete(r: AccommodationReservationCalendar) -> anyhow::Result<()> { pub async fn delete(r: AccommodationReservationCalendar) -> anyhow::Result<()> {
db_connection::execute(|conn| { db_connection::execute(|conn| {

View File

@ -129,9 +129,9 @@ pub async fn update_membership(membership: &Membership) -> anyhow::Result<()> {
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
pub struct FamilyMember { pub struct FamilyMember {
#[serde(flatten)] #[serde(flatten)]
membership: Membership, pub membership: Membership,
user_name: String, pub user_name: String,
user_mail: String, pub user_mail: String,
} }
/// Get information about the users of a family /// Get information about the users of a family