diff --git a/Cargo.lock b/Cargo.lock
index 14bb877..6ae4c9e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1112,6 +1112,7 @@ dependencies = [
"config",
"fake",
"hyper 1.2.0",
+ "linkify",
"once_cell",
"quickcheck",
"quickcheck_macros",
@@ -1165,6 +1166,15 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+[[package]]
+name = "linkify"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
diff --git a/Cargo.toml b/Cargo.toml
index 5c621aa..fe81084 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,12 +19,10 @@ hyper = { version = "1.2.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
serde-aux = "4"
-# serde_with = "3"
# Axum
axum = { version = "0.7" }
tower = { version = "0.4" }
tower-http = { version = "0.5", features = ["trace", "request-id", "util"] }
-# tower-cookies = "0.10"
# Others
config = "0.14"
uuid = { version = "1", features = ["v4", "fast-rng"] }
@@ -38,9 +36,6 @@ unicode-segmentation = "1"
strum_macros = "0.26"
validator = "0.16"
-# async-trait = "0.1"
-# strum_macros = "0.25"
-
[dependencies.sqlx]
version = "0.7"
default-features = false
@@ -68,3 +63,4 @@ quickcheck_macros = "1.0.0"
rand = "0.8.5"
wiremock = "0.6.0"
serde_json = "1"
+linkify = "0.10"
diff --git a/src/routes/subscriptions.rs b/src/routes/subscriptions.rs
index 6dd1fbb..7167657 100644
--- a/src/routes/subscriptions.rs
+++ b/src/routes/subscriptions.rs
@@ -56,12 +56,20 @@ pub async fn subscribe(
if insert_subscriber(&db_pool, &new_subscriber).await.is_err() {
return (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong.").into_response();
}
+ let confirmation_link = "https://my-api.com/subscriptions/confirm";
if email_client
.send_email(
new_subscriber.email,
"Welcome!",
- "Welcome to our newsletter!",
- "Welcome to our newsletter!",
+ &format!(
+ "Welcome to our newsletter!
\
+ Click here to confirm your subscription.",
+ confirmation_link,
+ ),
+ &format!(
+ "Welcome to our newsletter! \nVisit {} to confirm your subscription.",
+ confirmation_link,
+ ),
)
.await
.is_err()
diff --git a/tests/api/subscriptions.rs b/tests/api/subscriptions.rs
index bfa8587..e9bae61 100644
--- a/tests/api/subscriptions.rs
+++ b/tests/api/subscriptions.rs
@@ -2,6 +2,41 @@ use crate::helpers::spawn_app;
use wiremock::matchers::{method, path};
use wiremock::{Mock, ResponseTemplate};
+#[tokio::test]
+async fn subscribe_sends_a_confirmation_email_with_a_link() {
+ //Arrange
+ let app = spawn_app().await;
+ let body = "name=le%20guin&email=ursula_le_guin%40gmail.com";
+
+ Mock::given(path("/email"))
+ .and(method("POST"))
+ .respond_with(ResponseTemplate::new(200))
+ .mount(&app.email_server)
+ .await;
+
+ // Act
+ app.post_subscriptions(body.into()).await;
+
+ // Assert
+ // Get the first intercepted request
+ let email_request = &app.email_server.received_requests().await.unwrap()[0];
+ // Parse the body as JSON, starting from raw bytes
+ let body: serde_json::Value = serde_json::from_slice(&email_request.body).unwrap();
+ // Extract the link from one of the request fields.
+ let get_link = |s: &str| {
+ let links: Vec<_> = linkify::LinkFinder::new()
+ .links(s)
+ .filter(|l| *l.kind() == linkify::LinkKind::Url)
+ .collect();
+ assert_eq!(links.len(), 1);
+ links[0].as_str().to_owned()
+ };
+ let html_link = get_link(body["HtmlBody"].as_str().unwrap());
+ let text_link = get_link(body["TextBody"].as_str().unwrap());
+ // The two links should be identical
+ assert_eq!(html_link, text_link);
+}
+
#[tokio::test]
async fn subscribe_sends_a_confirmation_email_for_valid_data() {
// Arrange