Email admins on new account registration

This commit is contained in:
Chris Jean-Marie 2024-10-31 19:17:06 +00:00
parent d9d3834e8b
commit 6417a0445c
4 changed files with 45 additions and 37 deletions

View File

@ -1,6 +1,6 @@
use dotenvy::var; use dotenvy::var;
use lettre::{ use lettre::{
message::{header::ContentType, Mailbox}, message::{header::{ContentType, HeaderName, HeaderValue},Mailbox},
transport::smtp::authentication::Credentials, transport::smtp::authentication::Credentials,
Message, SmtpTransport, Transport, Message, SmtpTransport, Transport,
}; };
@ -8,7 +8,7 @@ use lettre::{
fn create_mailer() -> SmtpTransport { fn create_mailer() -> SmtpTransport {
// Get the server settings from the env file // Get the server settings from the env file
let smtp_server_name = var("SMTP_SERVER_NAME").expect("SMTP_SERVER_NAME not set"); let smtp_server_name = var("SMTP_SERVER_NAME").expect("SMTP_SERVER_NAME not set");
let smtp_server_port = var("SMTP_SERVER_PORT").expect("SMTP_SERVER_PORT not set"); //let smtp_server_port = var("SMTP_SERVER_PORT").expect("SMTP_SERVER_PORT not set");
// Get the username and password from the env file // Get the username and password from the env file
let username = var("EMAIL_USERNAME").expect("EMAIL_USERNAME not set"); let username = var("EMAIL_USERNAME").expect("EMAIL_USERNAME not set");
@ -26,20 +26,26 @@ fn create_mailer() -> SmtpTransport {
.build() .build()
} }
pub fn send_email(subject: String, recipient: String, body: String) { pub fn send_emails(subject: String, recipients: String, body: String) {
let username = var("EMAIL_USERNAME").expect("EMAIL_USERNAME not set"); let username = var("EMAIL_USERNAME").expect("EMAIL_USERNAME not set");
// Build the email // Build the email
let email = Message::builder() let mut email = Message::builder()
.from(username.parse::<Mailbox>().unwrap()) .from(username.parse::<Mailbox>().unwrap())
.to(recipient.parse::<Mailbox>().unwrap()) .to(username.parse::<Mailbox>().unwrap())
.subject(subject) .subject(subject)
.header(ContentType::TEXT_HTML) .header(ContentType::TEXT_HTML)
.body(body.to_string()) .body(body)
.unwrap(); .unwrap();
let headers = email.headers_mut();
//headers.set(To(recipients));
headers.insert_raw(HeaderValue::new(HeaderName::new_from_ascii_str("To"), recipients.to_owned()));
let mailer = create_mailer(); let mailer = create_mailer();
// TODO: Add logging
// Send the email // Send the email
match mailer.send(&email) { match mailer.send(&email) {
Ok(_) => println!("Basic email sent!"), Ok(_) => println!("Basic email sent!"),
@ -47,19 +53,4 @@ pub fn send_email(subject: String, recipient: String, body: String) {
println!("Basic email failed to send. {:?}", error); println!("Basic email failed to send. {:?}", error);
} }
} }
}
pub fn send_emails(subject: String, recipients: Vec<String>, body: String) {
let username = var("EMAIL_USERNAME").expect("EMAIL_USERNAME not set");
// Build the email
let mut email = Message::builder()
.from(username.parse::<Mailbox>().unwrap())
.subject(subject)
.header(ContentType::TEXT_HTML)
.body(body);
for recipient in recipients {
email = email.to(recipient.parse::<Mailbox>().unwrap());
}
} }

View File

@ -22,6 +22,9 @@ use sqlx::SqlitePool;
use std::collections::HashMap; use std::collections::HashMap;
use uuid::Uuid; use uuid::Uuid;
use crate::user::get_useremails_by_role;
use crate::email::send_emails;
use super::{AppError, UserData}; use super::{AppError, UserData};
fn get_client(hostname: String) -> Result<BasicClient, AppError> { fn get_client(hostname: String) -> Result<BasicClient, AppError> {
@ -201,16 +204,18 @@ pub async fn google_auth_return(
// Add public role // Add public role
sqlx::query("INSERT INTO user_roles (created_at, created_by, updated_at, updated_by, user_id, role_id) VALUES (?, ?, ?, ?, ?, ?)") sqlx::query("INSERT INTO user_roles (created_at, created_by, updated_at, updated_by, user_id, role_id) VALUES (?, ?, ?, ?, ?, ?)")
.bind(now) .bind(now)
.bind(0 as i64)// Created by system .bind(0 as i64)// Created by system
.bind(now) .bind(now)
.bind(0 as i64) // Updated by system .bind(0 as i64) // Updated by system
.bind(query.0) .bind(query.0)
.bind("1") .bind("1")
.execute(&db_pool) .execute(&db_pool)
.await?; .await?;
// TODO - send email to admin regarding new user registration // send email to admin regarding new user registration
let recipients = get_useremails_by_role("admin".to_string(), &db_pool).await;
send_emails("Jean-Marie website - New user registration".to_string(), recipients, "A new user has registered".to_string());
query.0 query.0
}; };

View File

@ -2,7 +2,6 @@ use std::net::SocketAddr;
use axum::{ use axum::{
middleware, routing::{get, get_service}, Extension, Router middleware, routing::{get, get_service}, Extension, Router
}; };
use email::send_email;
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool}; use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
use sqlx::migrate::Migrator; use sqlx::migrate::Migrator;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
@ -21,6 +20,7 @@ use google_oauth::{login, logout, google_auth_return};
use routes::{about, contact, cottagecalendar, dashboard, index, profile, user_profile, useradmin}; use routes::{about, contact, cottagecalendar, dashboard, index, profile, user_profile, useradmin};
use user::{add_user_role, delete_user_role, UserData}; use user::{add_user_role, delete_user_role, UserData};
use wishlist::{user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, user_wishlist_received_item, wishlists}; use wishlist::{user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, user_wishlist_received_item, wishlists};
//use email::send_emails;
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
@ -71,12 +71,13 @@ async fn main() {
.route("/logout", get(logout)) .route("/logout", get(logout))
.route("/google_auth_return", get(google_auth_return)) .route("/google_auth_return", get(google_auth_return))
.route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), inject_user_data)) .route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), inject_user_data))
.with_state(app_state.db_pool) .with_state(app_state.db_pool.clone())
.layer(Extension(user_data)) .layer(Extension(user_data))
; ;
// Send email indicating server has started // Send email indicating server has started
//send_email("Server started".to_string(), "chris@jean-marie.ca".to_string(), "Server has been started".to_string()); //let recipients = get_useremails_by_role("admin".to_string(), &app_state.db_pool).await;
//send_emails("Server started".to_string(), recipients, "Server has been started".to_string());
// run it // run it
let addr = SocketAddr::from(([0, 0, 0, 0], 40192)); let addr = SocketAddr::from(([0, 0, 0, 0], 40192));

View File

@ -8,11 +8,11 @@ use chrono::Utc;
///User related structs and functions ///User related structs and functions
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{prelude::FromRow, SqlitePool}; use sqlx::{FromRow, SqlitePool};
use crate::middlewares::is_authorized; use crate::middlewares::is_authorized;
#[derive(Default, Clone, Debug, FromRow, Serialize, Deserialize)] #[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
pub struct UserData { pub struct UserData {
pub id: i64, pub id: i64,
pub created_at: i64, pub created_at: i64,
@ -36,7 +36,7 @@ pub struct RoleData {
pub description: String, pub description: String,
} }
#[derive(Default, Clone, Debug, FromRow, Serialize, Deserialize)] #[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct UserRoles { pub struct UserRoles {
pub id: i64, pub id: i64,
pub created_at: i64, pub created_at: i64,
@ -47,7 +47,7 @@ pub struct UserRoles {
pub role_id: i64, pub role_id: i64,
} }
#[derive(Default, Clone, Debug, FromRow, Serialize, Deserialize)] #[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
pub struct UserRolesDisplay { pub struct UserRolesDisplay {
pub id: i64, pub id: i64,
pub created_at: i64, pub created_at: i64,
@ -60,7 +60,7 @@ pub struct UserRolesDisplay {
pub role_name: String, pub role_name: String,
} }
#[derive(Default, Clone, Debug, FromRow, Serialize, Deserialize)] #[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
pub struct UserWishlistItem { pub struct UserWishlistItem {
pub id: i64, pub id: i64,
pub created_at: i64, pub created_at: i64,
@ -135,7 +135,7 @@ pub async fn add_user_role(
.unwrap(); .unwrap();
// TODO - send email to user regarding role changes // TODO - send email to user regarding role changes
let redirect_url = format!("/users/{user_id}"); let redirect_url = format!("/users/{user_id}");
Redirect::to(&redirect_url).into_response() Redirect::to(&redirect_url).into_response()
} else { } else {
@ -165,7 +165,8 @@ pub async fn delete_user_role(
pub async fn get_user_wishlist_items(user_id: i64, db_pool: &SqlitePool) -> Vec<UserWishlistItem> { pub async fn get_user_wishlist_items(user_id: i64, db_pool: &SqlitePool) -> Vec<UserWishlistItem> {
// Get wish list items for the user // Get wish list items for the user
let user_wishlist_items = sqlx::query_as( let user_wishlist_items = sqlx::query_as(
r#"select id, created_at, created_by, updated_at, updated_by, user_id, item, item_url, purchased_by, received_at from wishlist_items where user_id = ?"# r#"select id, created_at, created_by, updated_at, updated_by, user_id, item, item_url, purchased_by, received_at
from wishlist_items where user_id = ?"#
) )
.bind(user_id) .bind(user_id)
.fetch_all(db_pool) .fetch_all(db_pool)
@ -174,3 +175,13 @@ pub async fn get_user_wishlist_items(user_id: i64, db_pool: &SqlitePool) -> Vec<
user_wishlist_items user_wishlist_items
} }
pub async fn get_useremails_by_role(role_name: String, db_pool: &SqlitePool) -> String {
let useremails: String = sqlx::query_scalar(r#"select group_concat(u.email) as email from user_roles ur, roles r, users u where u.id = ur.user_id and r.id = ur.role_id and r.name = ?"#)
.bind(role_name)
.fetch_one(db_pool)
.await
.unwrap();
useremails
}