diff --git a/src/email_client.rs b/src/email_client.rs index 7bfe71e..f2854d6 100644 --- a/src/email_client.rs +++ b/src/email_client.rs @@ -31,7 +31,7 @@ impl EmailClient { pub async fn send_email( &self, - recipient: SubscriberEmail, + recipient: &SubscriberEmail, subject: &str, html_content: &str, text_content: &str, @@ -140,7 +140,7 @@ mod tests { // Act let _ = email_client - .send_email(email(), &subject(), &content(), &content()) + .send_email(&email(), &subject(), &content(), &content()) .await; // Assert @@ -161,7 +161,7 @@ mod tests { // Act let result = email_client - .send_email(email(), &subject(), &content(), &content()) + .send_email(&email(), &subject(), &content(), &content()) .await; // Assert @@ -182,7 +182,7 @@ mod tests { // Act let result = email_client - .send_email(email(), &subject(), &content(), &content()) + .send_email(&email(), &subject(), &content(), &content()) .await; // Assert @@ -203,7 +203,7 @@ mod tests { // Act let result = email_client - .send_email(email(), &subject(), &content(), &content()) + .send_email(&email(), &subject(), &content(), &content()) .await; // Assert diff --git a/src/routes/newsletters.rs b/src/routes/newsletters.rs index 8e730bd..24af47a 100644 --- a/src/routes/newsletters.rs +++ b/src/routes/newsletters.rs @@ -1,15 +1,25 @@ +use std::sync::Arc; + +use anyhow::Context; use axum::extract::rejection::JsonRejection; -use axum::extract::{FromRequest, Json}; +use axum::extract::{FromRequest, Json, State}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; +use entity::prelude::Subscriptions; +use entity::subscriptions; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, SelectColumns}; use serde::Deserialize; +use crate::domain::SubscriberEmail; use crate::routes::error_chain_fmt; +use crate::startup::ApplicationState; #[derive(thiserror::Error)] pub enum PublishError { #[error(transparent)] JsonRejection(#[from] JsonRejection), + #[error(transparent)] + UnexpectedError(#[from] anyhow::Error), } impl std::fmt::Debug for PublishError { @@ -24,6 +34,7 @@ impl IntoResponse for PublishError { match self { PublishError::JsonRejection(_) => StatusCode::BAD_REQUEST.into_response(), + PublishError::UnexpectedError(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), } } } @@ -45,7 +56,61 @@ pub struct Content { } pub async fn publish_newsletters( - PublishBody(_body): PublishBody, + State(state): State>, + PublishBody(body): PublishBody, ) -> Result { + let subscribers = get_confirmed_subscribers(&state.db_connection) + .await + .context("Failed to get confirmed subscribers")?; + + for subscriber in subscribers { + match subscriber { + Ok(subscriber) => { + state + .email_client + .send_email( + &subscriber.email, + &body.title, + &body.content.html, + &body.content.text, + ) + .await + // `with_context` is lazy so it only allocate memory when email delivery fails + .with_context(|| { + format!("Failed to send newsletter issue to {}", subscriber.email) + })?; + } + Err(error) => { + tracing::warn!( + error.cause_chain = ?error, + "Skipping a confirmed subscriber. The stored contact details are invalid" + ) + } + } + } + Ok(StatusCode::OK.into_response()) } + +struct ConfirmedSubscriber { + email: SubscriberEmail, +} + +#[tracing::instrument(name = "Get confirmed subscribers", skip(db_connection))] +async fn get_confirmed_subscribers( + db_connection: &DatabaseConnection, +) -> Result>, anyhow::Error> { + let confirmed_subscribers = Subscriptions::find() + .select_column(subscriptions::Column::Email) + .filter(subscriptions::Column::Status.eq("confirmed")) + .all(db_connection) + .await? + .into_iter() + .map(|row| match SubscriberEmail::parse(row.email) { + Ok(email) => Ok(ConfirmedSubscriber { email }), + Err(error) => Err(anyhow::anyhow!(error)), + }) + .collect(); + + Ok(confirmed_subscribers) +} diff --git a/src/routes/subscriptions.rs b/src/routes/subscriptions.rs index 591ebb8..3702aae 100644 --- a/src/routes/subscriptions.rs +++ b/src/routes/subscriptions.rs @@ -183,7 +183,7 @@ pub async fn send_confirmation_email( email_client .send_email( - new_subscriber.email, + &new_subscriber.email, "Welcome!", &html_body, &plain_text_body,