aboutsummaryrefslogtreecommitdiff
path: root/comments/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'comments/src/main.rs')
-rw-r--r--comments/src/main.rs60
1 files changed, 59 insertions, 1 deletions
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<MailCtx>,
+}
+
+#[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<Ctx>,
Form(post_comment): Form<PostComment>) -> Result<Redirect,(StatusCode,String)> {
@@ -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 <comments@mfashby.net>".parse().unwrap())
+ .to(mail_opts.notification_address.parse().unwrap())
+ .subject(format!("New comment on {}", post_comment.url))
+ .body(mail_body)
+ .unwrap();
+ let mailer: AsyncSmtpTransport<Tokio1Executor> =
+ AsyncSmtpTransport::<Tokio1Executor>::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))
}