#![allow(unused_variables)] use std::fmt::Display; use std::str::FromStr; use chrono::{Local, NaiveDate, NaiveTime, Weekday}; use lazy_static::lazy_static; use regex::Regex; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use uuid::Uuid; use validator::{Validate, ValidationError}; use crate::error::AppError; #[derive(Serialize, Deserialize, Clone, Debug)] pub enum ApiResponse<T> { Data(T), Error(String) } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct Company { id: i32, #[validate(length(min = 1,message = "Name cannot be empty"))] pub name: String, #[validate(length(min = 1,message = "Street cannot be empty"))] pub street: String, #[validate(length(min = 1,message = "House number cannot be empty"))] pub house_number: String, pub zip_code: String, pub city: String, } impl Company { pub fn id(&self) -> i32 { self.id } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct User { id: i32, pub login: String, pub password: String, pub full_name: Option<String>, #[validate(email(message = "Enter valid email address"))] pub email: Option<String>, pub admin: bool, pub get_emails: bool, } impl User { pub fn id(&self) -> i32 { self.id } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)] pub struct UserProfile { #[validate(length(min = 1,message = "Username cannot be empty"))] login: String, #[validate(must_match(other = "password_ver", message = "Passwords doesn't match"))] password: Option<String>, password_ver: Option<String>, full_name: String, #[validate(email(message = "Enter valid email address"))] email: String, get_emails: Option<String>, admin: Option<String> } impl UserProfile { pub fn login(&self) -> &str { &self.login } pub fn full_name(&self) -> &str { &self.full_name } pub fn email(&self) -> &str { &self.email } pub fn get_emails(&self) -> bool { self.get_emails.is_some() } pub fn admin(&self) -> bool { self.admin.is_some() } pub fn password(&self) -> &Option<String> { &self.password } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)] pub struct PwdChange { login: String, old_password: String, #[validate(length(min = 1, message = "Enter new password"), must_match(other = "password_ver", message = "Passwords doesn't match"))] password: String, password_ver: String } impl PwdChange { pub fn login(&self) -> &str { &self.login } pub fn old_password(&self) -> &str { &self.old_password } pub fn password(&self) -> &str { &self.password } pub fn password_ver(&self) -> &str { &self.password_ver } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct OpeningHour { id: i32, pub day: i32, pub from: NaiveTime, pub to: NaiveTime, pub discount: Option<i32> } impl OpeningHour { pub fn id(&self) -> i32 { self.id } } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct DayHours(pub Vec<DayHour>); impl DayHours { pub fn try_new(hours: &str) -> Result<Self, AppError> { if hours.is_empty() { return Ok(Self(Vec::new())) } let times = hours.split(",") .map(|h| h.trim()) .map(|h| DayHour::try_from(h)) .collect::<Vec<_>>(); if times.contains(&Err(AppError::HourParseError)) { return Err(AppError::HourParseError) } Ok(Self(times.into_iter().map(|h| h.unwrap()).collect())) } pub fn hours(&self) -> &Vec<DayHour> { &self.0 } } impl Display for DayHours { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.0.is_empty() { return write!(f, "{}", "".to_string()) } let str = self.0.iter().map(|h| { let discount = if let Some(d) = h.discount() { format!(" ({})", d).to_string() } else { "".to_string() }; format!("{} - {}{}", h.from().format("%H:%M"), h.to().format("%H:%M"), discount).to_string() }) .collect::<Vec<String>>() .join(", "); write!(f, "{}", str) } } #[derive(Eq, PartialEq, Serialize, Deserialize, Clone, Debug)] pub struct DayHour { from: NaiveTime, to: NaiveTime, discount: Option<i32> } impl DayHour { pub fn new(from: NaiveTime, to: NaiveTime, discount: Option<i32>) -> Self { Self { from, to, discount } } pub fn from(&self) -> NaiveTime { self.from } pub fn to(&self) -> NaiveTime { self.to } pub fn discount(&self) -> Option<i32> { self.discount } } impl TryFrom<String> for DayHour { type Error = AppError; fn try_from(value: String) -> Result<Self, Self::Error> { value.as_str().try_into() } } impl TryFrom<&str> for DayHour { type Error = AppError; fn try_from(value: &str) -> Result<Self, Self::Error> { if value.is_empty() { return Err(AppError::HourParseError) } let times = value.split("-") .map(|t| t.trim()).collect::<Vec<_>>(); if times.len() != 2 { return Err(AppError::HourParseError) } let from = NaiveTime::parse_from_str(times.get(0).unwrap(), "%H:%M"); let to = NaiveTime::parse_from_str(times.get(1).unwrap(), "%H:%M"); if from.is_err() || to.is_err() { return Err(AppError::HourParseError) } Ok(DayHour { from: from.unwrap(), to: to.unwrap(), discount: None }) } } fn validate_hours(value: &WeekHours) -> Result<(), ValidationError> { if value.hours().is_empty() { return Ok(()) } if let Ok(h) = DayHours::try_new(value.hours()) { for hr in h.hours() { if hr.from() >= hr.to() { return Err(ValidationError::new("TO_BEFORE_FROM")) } } Ok(()) } else { Ok(()) } } lazy_static! { static ref RE_HOURS: Regex = Regex::new(r"^$|(^\d{2}:\d{2} ?- ?\d{2}:\d{2} ?(\(\d+\))?,? ?)+").unwrap(); } #[derive(Clone, Serialize, Deserialize, Debug, Validate)] #[validate(schema(function = "validate_hours", message = "Time 'to' must be after time 'from'"))] pub struct WeekHours { day: Weekday, #[validate(regex(path = "RE_HOURS", message = "Hours must be in HH:MM - HH:MM format"))] hours: String } impl Default for WeekHours { fn default() -> Self { Self { day: Weekday::Mon, hours: String::default() } } } impl WeekHours { pub fn new(day: Weekday, hours: Vec<DayHour>) -> Self { Self { day, hours: DayHours(hours).to_string() } } pub fn day(&self) -> Weekday { self.day } pub fn hours(&self) -> &str { &self.hours } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::Type))] #[cfg_attr(feature = "ssr", sqlx(type_name = "slot_type"))] pub enum SlotType { Quarter, Half, #[default] Hour, Day } fn def_true() -> bool { true } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct ResProperty { id: i32, #[validate(length(min = 1,message = "Name cannot be empty"))] pub name: String, pub description: String, pub price: Decimal, pub slot: SlotType, #[serde(default = "def_true")] pub allow_multi: bool, #[serde(default = "def_true")] pub active: bool } impl ResProperty { pub fn id(&self) -> i32 { self.id } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)] pub struct PublicFormData { pub property: ResProperty, pub hours: Vec<DayHour>, pub reservations: Vec<Reservation> } fn empty_slots() -> Vec<String> { vec![] } fn validate_date(date: &NaiveDate) -> Result<(), ValidationError> { if date < &Local::now().date_naive() { Err(ValidationError::new("date_in_past")) } else { Ok(()) } } #[derive(Clone, Serialize, Deserialize, Debug, Validate)] pub struct CrReservation { #[validate(custom(function = "validate_date", message = "Date can't be in past"))] date: NaiveDate, #[validate(length(min = 1,message = "Select at last one time slot"))] #[serde(default = "empty_slots")] slots: Vec<String>, #[validate(length(min = 1,message = "Enter your full name"))] full_name: String, #[validate(email(message = "Enter valid email address"))] email: String, #[validate(length(min = 1,message = "Enter your phone number"))] phone: String, note: String } #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct TmCheck { pub from: NaiveTime, pub to: NaiveTime } impl FromStr for TmCheck { type Err = AppError; fn from_str(s: &str) -> Result<Self, Self::Err> { let times = s.split("-").collect::<Vec<_>>(); if times.len() != 2 { return Err(AppError::HourParseError); } Ok(TmCheck{ from: NaiveTime::from_str(times.get(0).unwrap_or(&""))?, to: NaiveTime::from_str(times.get(1).unwrap_or(&""))? }) } } impl CrReservation { pub fn date(&self) -> NaiveDate { self.date } pub fn slots(&self) -> &Vec<String> { &self.slots } pub fn full_name(&self) -> &str { &self.full_name } pub fn email(&self) -> &str { &self.email } pub fn phone(&self) -> &str { &self.phone } pub fn note(&self) -> &str { &self.note } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct Customer { id: i32, pub full_name: String, pub email: String, pub phone: String, pub discount: i32 } impl Customer { pub fn id(&self) -> i32 { self.id } pub fn new(id: i32, full_name: String, email: String, phone: String, discount: i32) -> Self { Self { id, full_name, email, phone, discount } } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::Type))] #[cfg_attr(feature = "ssr", sqlx(type_name = "reservation_state"))] pub enum ReservationState { #[default] New, Approved, Canceled, } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct Reservation { id: i32, pub from: NaiveTime, pub to: NaiveTime, pub property: i32, pub summary: i32, } #[derive(Clone, Serialize, Deserialize, Debug, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct ResPropertyView { pub name: String, pub description: String } #[derive(Clone, Serialize, Deserialize, Debug, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct ResWithProperty { #[cfg_attr(feature = "ssr", sqlx(flatten))] pub reservation: Reservation, #[cfg_attr(feature = "ssr", sqlx(flatten))] pub property: ResPropertyView } pub struct Reservations(Vec<Reservation>); // Transform slots to reservations impl Reservations { pub fn new() -> Self { Reservations(vec![]) } pub fn is_empty(&self) -> bool { self.0.is_empty() } pub fn add_slot(&mut self, check: &TmCheck, property: i32) { let mut added = false; self.0.iter_mut().for_each(|r| { if r.property == property && r.to == check.from { r.to = check.to; added = true; } }); if !added { self.0.push(Reservation { from: check.from, to: check.to, property, id: 0, summary: 0, }); } } pub fn reservations(&self) -> &Vec<Reservation> { &self.0 } } impl Reservation { pub fn new(from: NaiveTime, to: NaiveTime, property: i32) -> Self { Self { id: 0, from, to, property, summary: 0 } } pub fn id(&self) -> i32 { self.id } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct ReservationSum { id: i32, pub uuid: Uuid, pub date: NaiveDate, pub customer: i32, pub price: Decimal, pub state: ReservationState, pub date_create: NaiveDate, pub edited_by: Option<i32>, pub note: Option<String>, } impl ReservationSum { pub fn id(&self) -> i32 { self.id } } #[derive(Clone, Serialize, Deserialize, Debug, Default)] pub struct ResSumWithItems { pub summary: ReservationSum, pub customer: Customer, pub reservations: Vec<ResWithProperty> } #[derive(Clone, Serialize, Deserialize, Debug, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::Type))] #[cfg_attr(feature = "ssr", sqlx(type_name = "message_type"))] pub enum MessageType { #[default] NewReservation, NewReservationCust, ReservationApp, ReservationCanceled, } impl Display for MessageType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", match self { MessageType::NewReservation => {"NewReservation"} MessageType::NewReservationCust => {"NewReservationCust"} MessageType::ReservationApp => {"ReservationApp"} MessageType::ReservationCanceled => {"ReservationCanceled"} }) } } #[derive(Clone, Serialize, Deserialize, Debug, Default, Validate)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct Message { id: i32, pub msg_type: MessageType, #[validate(length(min = 1,message = "Enter mail subject"))] pub subject: String, #[validate(length(min = 1,message = "Enter text"))] pub text: String, } impl Message { pub fn id(&self) -> i32 { self.id } }