From fdfb0e65d3de718beddcf3c882d716b7aff6a051 Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Sat, 31 Dec 2022 16:30:52 +0000 Subject: Add email notifications to comments --- comments/src/main.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) (limited to 'comments/src/main.rs') diff --git a/comments/src/main.rs b/comments/src/main.rs index 2bc11fa..7064bca 100644 --- a/comments/src/main.rs +++ b/comments/src/main.rs @@ -15,6 +15,10 @@ use axum::{ routing::get, Router, }; +use lettre::{ + AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, + transport::smtp::authentication::Credentials +}; use serde::Deserialize; use sqlx::{ postgres::{PgPool, PgPoolOptions}, @@ -25,11 +29,20 @@ use sqlx::{ }; use std::{net::SocketAddr, time::Duration}; + // Useful global thingies #[derive(Clone)] struct Ctx { pool: PgPool, base_path: String, + mail_opts: Option, +} + +#[derive(Clone)] +struct MailCtx { + notification_address: String, + smtp_server: String, + smtp_credentials: Credentials, } #[tokio::main] @@ -40,6 +53,18 @@ async fn main() { let db_connection_str = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgres://postgres:password@localhost".to_string()); + let notification_address = std::env::var("NOTIFICATION_ADDRESS").ok(); + let mail_opts = notification_address.map(|na| { + let smtp_server = std::env::var("SMTP_SERVER").expect("SMTP_SERVER not supplied!"); + let smtp_username = std::env::var("SMTP_USERNAME").expect("SMTP_USERNAME not supplied!"); + let smtp_password = std::env::var("SMTP_PASSWORD").expect("SMTP_PASSWORD not supplied!"); + MailCtx{ + smtp_server: smtp_server, + smtp_credentials: Credentials::new(smtp_username, smtp_password), + notification_address: na, + } + }); + let pool = PgPoolOptions::new() .max_connections(5) .connect_timeout(Duration::from_secs(3)) @@ -51,7 +76,7 @@ async fn main() { .await .expect("failed to run migrations"); - let ctx = Ctx {pool, base_path: base_path.clone()}; + let ctx = Ctx {pool, base_path: base_path.clone(), mail_opts}; let app = Router::new() .nest(&base_path, Router::new() @@ -142,6 +167,14 @@ struct CapchaAnswer { answer:String } +#[derive(Template)] +#[template(path = "notification.txt")] +struct Notification<'a> { + url: &'a str, + comment: &'a str, + author: &'a str, +} + async fn post_comments( State(ctx): State, Form(post_comment): Form) -> Result { @@ -158,6 +191,31 @@ async fn post_comments( .execute(&ctx.pool) .await .map_err(internal_error)?; + + if let Some(mail_opts) = ctx.mail_opts { + let mail_body = Notification{ + url: &post_comment.url, + author: &post_comment.author, + comment: &post_comment.comment + }.render().unwrap(); + let email = Message::builder() + // TODO should be a config value + .from("Comments Service ".parse().unwrap()) + .to(mail_opts.notification_address.parse().unwrap()) + .subject(format!("New comment on {}", post_comment.url)) + .body(mail_body) + .unwrap(); + let mailer: AsyncSmtpTransport = + AsyncSmtpTransport::::relay(&mail_opts.smtp_server) + .unwrap() + .credentials(mail_opts.smtp_credentials) + .build(); + match mailer.send(email).await { + Ok(_) => (), + Err(e) => eprintln!("Could not send email: {:?}", e), + } + }; + Ok(Redirect::temporary(&post_comment.url)) } -- cgit v1.2.3-ZIG