diff --git a/src/app.rs b/src/app.rs index 247151f..092a64b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,7 @@ use crate::components::admin_portal::AdminPortal; use crate::components::header::Header; use crate::components::user_menu::MenuOpener; use crate::pages::login::Login; +use crate::pages::mail_settings::MailSettings; use crate::pages::public::Public; #[derive(Clone, Copy)] @@ -63,9 +64,9 @@ pub fn App() -> impl IntoView { // Provides context that manages stylesheets, titles, meta tags, etc. provide_meta_context(); init_locales(); - provide_context(MenuHelper::new()); - provide_context(MenuOpener::new()); - provide_context(DialogHelper::new()); + provide_context(MenuHelper::new()); // Only one menu can be opened + provide_context(MenuOpener::new()); // Drawer opener + provide_context(DialogHelper::new()); // Gray dialog background view! {
@@ -85,6 +86,11 @@ pub fn App() -> impl IntoView { }/> + + + + }/> diff --git a/src/backend/data.rs b/src/backend/data.rs index 2404db4..e578990 100644 --- a/src/backend/data.rs +++ b/src/backend/data.rs @@ -538,19 +538,41 @@ pub struct ResSumWithItems { pub reservations: Vec } -/* +#[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: u16, - msg_type: MessageType, - subject: String, - text: String, + 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 + } +} diff --git a/src/backend/mail.rs b/src/backend/mail.rs new file mode 100644 index 0000000..4cccd63 --- /dev/null +++ b/src/backend/mail.rs @@ -0,0 +1,81 @@ +use cfg_if::cfg_if; +use leptos::*; +use validator::Validate; +use crate::backend::data::{ApiResponse, Message, MessageType}; +use crate::components::data_form::ForValidation; + +cfg_if! { if #[cfg(feature = "ssr")] { + use sqlx::{PgPool, query, query_as}; + use sqlx::Error; + use crate::backend::get_pool; + use crate::error::AppError; + use log::info; + + pub async fn message_for_type(msg_type: &MessageType, pool: &PgPool) -> Result { + Ok(query_as::<_, Message>("SELECT * FROM message WHERE msg_type = $1") + .bind(msg_type) + .fetch_one(pool) + .await?) + } + + pub async fn init_message(msg_type: &MessageType, pool: &PgPool) -> Result<(), Error> { + query("INSERT INTO message(msg_type, subject, text) VALUES($1, '', '')") + .bind(msg_type) + .execute(pool) + .await?; + + Ok(()) + } + + pub async fn check_messages(pool: &PgPool) -> Result<(), AppError> { + let types = [ + MessageType::NewReservation, + MessageType::NewReservationCust, + MessageType::ReservationApp, + MessageType::ReservationCanceled]; + + for msg_type in types { + let msg = message_for_type(&msg_type, pool).await; + if let Err(e) = msg { + if matches!(e, Error::RowNotFound) { + info!("Creating initial message for type {:?}", msg_type); + init_message(&msg_type, pool).await?; + } else { + return Err(e.into()) + } + } + } + + Ok(()) + } +}} + +#[server] +pub async fn get_message(msg_type: MessageType) -> Result { + let pool = get_pool().await?; + + Ok(message_for_type(&msg_type, &pool).await?) +} + +#[server] +pub async fn update_message(message: Message) -> Result, ServerFnError> { + use crate::perm_check; + + perm_check!(is_admin); + let pool = get_pool().await?; + + query("UPDATE message SET subject = $1, text = $2 WHERE msg_type = $3") + .bind(message.subject) + .bind(message.text) + .bind(message.msg_type) + .execute(&pool) + .await?; + + Ok(ApiResponse::Data(())) +} + +impl ForValidation for UpdateMessage { + fn entity(&self) -> &dyn Validate { + &self.message + } +} \ No newline at end of file diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 8d34538..1b5d9c4 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -7,6 +7,7 @@ pub mod opening_hours; pub mod property; pub mod reservation; pub mod customer; +pub mod mail; #[macro_export] macro_rules! perm_check { diff --git a/src/components/admin_portal.rs b/src/components/admin_portal.rs index c10e7eb..0a70b78 100644 --- a/src/components/admin_portal.rs +++ b/src/components/admin_portal.rs @@ -6,9 +6,40 @@ use crate::locales::trl; use crate::pages::change_pwd::ChangePassword; use crate::pages::profile_edit::ProfileEdit; +#[component] +fn settings_menu(opener: MenuOpener) -> impl IntoView { + view! { + + } +} + #[component] pub fn AdminPortal(children: Children) -> impl IntoView { let user_menu = MenuOpener::new(); + let settings_menu = MenuOpener::new(); let (user, set_user) = create_signal(User::default()); let editor = DialogOpener::new(); let pw_changer = DialogOpener::new(); @@ -38,15 +69,15 @@ pub fn AdminPortal(children: Children) -> impl IntoView {