Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7dd5289

Browse files
chore: build out the nsend newsletter endpoint
1 parent 7d77a09 commit 7dd5289

File tree

4 files changed

+88
-9
lines changed

4 files changed

+88
-9
lines changed

‎src/domain/subscriber_email.rs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ impl AsRef<str> for SubscriberEmail {
1818
}
1919
}
2020

21+
impl std::fmt::Display for SubscriberEmail {
22+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23+
// We just forward to the Display implementation of
24+
// the wrapped String.
25+
self.0.fmt(f)
26+
}
27+
}
28+
2129
#[cfg(test)]
2230
mod tests {
2331
use super::*;

‎src/email_client.rs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl EmailClient {
3030
impl EmailClient {
3131
pub async fn send_email(
3232
&self,
33-
recipient: SubscriberEmail,
33+
recipient: &SubscriberEmail,
3434
subject: &str,
3535
html_content: &str,
3636
text_content: &str,
@@ -141,7 +141,7 @@ mod tests {
141141

142142
let _ = email_client
143143
.send_email(
144-
recipient,
144+
&recipient,
145145
&generate_subject(),
146146
&generate_content(),
147147
&generate_content(),
@@ -164,7 +164,7 @@ mod tests {
164164

165165
let outcome = email_client
166166
.send_email(
167-
subscriber_email,
167+
&subscriber_email,
168168
&generate_subject(),
169169
&generate_content(),
170170
&generate_content(),
@@ -189,7 +189,7 @@ mod tests {
189189

190190
let outcome = email_client
191191
.send_email(
192-
subscriber_email,
192+
&subscriber_email,
193193
&generate_subject(),
194194
&generate_content(),
195195
&generate_content(),
@@ -216,7 +216,7 @@ mod tests {
216216

217217
let outcome = email_client
218218
.send_email(
219-
subscriber_email,
219+
&subscriber_email,
220220
&generate_subject(),
221221
&generate_content(),
222222
&generate_content(),

‎src/routes/newsletters.rs‎

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,88 @@
1-
use actix_web::{web, HttpResponse};
1+
use crate::{domain::SubscriberEmail, email_client::EmailClient, routes::error_chain_fmt};
2+
use actix_web::http::StatusCode;
3+
use actix_web::{web, HttpResponse, ResponseError};
4+
use anyhow::Context;
5+
use sqlx::PgPool;
26

37
#[derive(serde::Deserialize)]
48
pub struct BodyData {
59
title: String,
610
content: Content,
711
}
812

13+
struct ConfirmedSubscriber {
14+
email: SubscriberEmail,
15+
}
16+
17+
#[tracing::instrument(name = "Get confirmed subscribers", skip(pool))]
18+
async fn get_confirmed_subscribers(
19+
pool: &PgPool,
20+
) -> Result<Vec<Result<ConfirmedSubscriber, anyhow::Error>>, anyhow::Error> {
21+
let confirmed_subscriber =
22+
sqlx::query!(r#"SELECT email FROM subscriptions WHERE status = 'confirmed'"#,)
23+
.fetch_all(pool)
24+
.await?
25+
.into_iter()
26+
.map(|r| match SubscriberEmail::parse(r.email) {
27+
Ok(email) => Ok(ConfirmedSubscriber { email }),
28+
Err(error) => Err(anyhow::anyhow!(error)),
29+
})
30+
.collect();
31+
Ok(confirmed_subscriber)
32+
}
33+
934
#[derive(serde::Deserialize)]
1035
pub struct Content {
1136
text: String,
1237
html: String,
1338
}
1439

15-
pub async fn publish_newsletter(_body: web::Json<BodyData>) -> HttpResponse {
16-
HttpResponse::Ok().finish()
40+
#[derive(thiserror::Error)]
41+
pub enum PublishError {
42+
#[error(transparent)]
43+
UnexpectedError(#[from] anyhow::Error),
44+
}
45+
46+
impl ResponseError for PublishError {
47+
fn status_code(&self) -> StatusCode {
48+
match self {
49+
PublishError::UnexpectedError(_) => StatusCode::INTERNAL_SERVER_ERROR,
50+
}
51+
}
52+
}
53+
54+
impl std::fmt::Debug for PublishError {
55+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56+
error_chain_fmt(self, f)
57+
}
58+
}
59+
60+
pub async fn publish_newsletter(
61+
body: web::Json<BodyData>,
62+
pool: web::Data<PgPool>,
63+
email_client: web::Data<EmailClient>,
64+
) -> Result<HttpResponse, PublishError> {
65+
let subscribers = get_confirmed_subscribers(&pool).await?;
66+
for subscriber in subscribers {
67+
match subscriber {
68+
Ok(subscriber) => {
69+
email_client
70+
.send_email(
71+
&subscriber.email,
72+
&body.title,
73+
&body.content.text,
74+
&body.content.html,
75+
)
76+
.await
77+
.with_context(|| {
78+
format!("Failed to send newsletter issue to {}", subscriber.email)
79+
})?;
80+
}
81+
82+
Err(error) => {
83+
tracing::warn!("Skipping invalid subscriber: {}", error);
84+
}
85+
}
86+
}
87+
Ok(HttpResponse::Ok().finish())
1788
}

‎src/routes/subscriptions.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ pub async fn send_confirmation_email(
136136

137137
email_client
138138
.send_email(
139-
new_subscriber.email,
139+
&new_subscriber.email,
140140
"Welcome!",
141141
&html_body_text,
142142
&plain_body_text,

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /