From e83d4adf54a77c800f3a438796a5974e55cc3f95 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Tue, 16 May 2023 10:23:23 +0200 Subject: [PATCH] feat(newsletter): support public double opt-in (#187) * feature(newsletter): support public double opt-in This das the route /api/v1/newsletter to allow public sign up to our newsletter, once we made it non-private basket * dynamic url * add test --- src/api/api_v1.rs | 5 +- src/api/newsletter.rs | 50 ++++++++++++++++-- tests/api/newsletter.rs | 52 +++++++++++++++++-- .../newsletter/basket_subscribe_error.json | 17 ++++++ 4 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 tests/test_specific_stubs/newsletter/basket_subscribe_error.json diff --git a/src/api/api_v1.rs b/src/api/api_v1.rs index 9135350d..b820c930 100644 --- a/src/api/api_v1.rs +++ b/src/api/api_v1.rs @@ -1,4 +1,6 @@ -use crate::api::newsletter::{is_subscribed, subscribe_handler, unsubscribe_handler}; +use crate::api::newsletter::{ + is_subscribed, subscribe_anonymous_handler, subscribe_handler, unsubscribe_handler, +}; use crate::api::ping::ping; use crate::api::root::root_service; use crate::api::search::search; @@ -22,5 +24,6 @@ pub fn api_v1_service() -> impl HttpServiceFactory { .service(web::resource("/search").route(web::get().to(search))) .service(web::resource("/whoami").route(web::get().to(whoami))) .service(web::resource("/ping").route(web::post().to(ping))) + .service(web::resource("/newsletter").route(web::post().to(subscribe_anonymous_handler))) .service(root_service()) } diff --git a/src/api/newsletter.rs b/src/api/newsletter.rs index 96e2dbe1..4ca906fe 100644 --- a/src/api/newsletter.rs +++ b/src/api/newsletter.rs @@ -1,6 +1,9 @@ use actix_identity::Identity; -use actix_web::{web::Data, HttpResponse}; -use basket::{Basket, YesNo}; +use actix_web::{ + web::{self, Data}, + HttpResponse, +}; +use basket::{Basket, SubscribeOpts, YesNo}; use diesel::PgConnection; use serde::{Deserialize, Serialize}; @@ -12,6 +15,7 @@ use crate::{ users::get_user, Pool, }, + settings::SETTINGS, }; const MDN_PLUS_LIST: &str = "mdnplus"; @@ -27,6 +31,11 @@ struct Subscribed { pub subscribed: bool, } +#[derive(Deserialize, Serialize)] +pub struct SubscriptionRequest { + pub email: String, +} + pub async fn subscribe_handler( pool: Data, user_id: Identity, @@ -40,13 +49,48 @@ pub async fn subscribe_handler( Ok(HttpResponse::NotImplemented().finish()) } +pub async fn subscribe_anonymous_handler( + basket: Data>, + subscription_req: web::Json, +) -> Result { + if let Some(basket) = &**basket { + basket + .subscribe( + &subscription_req.email, + vec![MDN_PLUS_LIST.into()], + Some(SubscribeOpts { + source_url: Some(format!( + "{}/en-US/newsletter", + &SETTINGS.application.document_base_url + )), + ..Default::default() + }), + ) + .await?; + + return Ok(HttpResponse::Created().json(Subscribed { subscribed: true })); + } + Ok(HttpResponse::NotImplemented().finish()) +} + pub async fn subscribe( conn: &mut PgConnection, user: &UserQuery, basket: &Basket, ) -> Result { basket - .subscribe_private(&user.email, vec![MDN_PLUS_LIST.into()], None) + .subscribe_private( + &user.email, + vec![MDN_PLUS_LIST.into()], + Some(SubscribeOpts { + optin: Some(YesNo::Y), + source_url: Some(format!( + "{}/en-US/settings", + &SETTINGS.application.document_base_url + )), + ..Default::default() + }), + ) .await?; db::settings::create_or_update_settings( conn, diff --git a/tests/api/newsletter.rs b/tests/api/newsletter.rs index 8f01b143..6bdd889b 100644 --- a/tests/api/newsletter.rs +++ b/tests/api/newsletter.rs @@ -1,14 +1,15 @@ -use crate::helpers::app::test_app_with_login; +use crate::helpers::app::{init_test, test_app_with_login}; use crate::helpers::db::reset; use crate::helpers::http_client::TestHttpClient; use crate::helpers::{read_json, wait_for_stubr}; use actix_web::test; use anyhow::Error; +use serde_json::json; use stubr::{Config, Stubr}; #[actix_rt::test] #[stubr::mock(port = 4321)] -async fn whoami_settings_test() -> Result<(), Error> { +async fn settings_newsletter_test() -> Result<(), Error> { let pool = reset()?; wait_for_stubr().await?; let app = test_app_with_login(&pool).await?; @@ -41,7 +42,10 @@ async fn whoami_settings_test() -> Result<(), Error> { drop(stubr); let stubr = Stubr::start_blocking_with( - vec!["tests/stubs", "tests/test_specific_stubs/newsletter"], + vec![ + "tests/stubs", + "tests/test_specific_stubs/newsletter/basket_lookup_user.json", + ], Config { port: Some(4321), latency: None, @@ -67,3 +71,45 @@ async fn whoami_settings_test() -> Result<(), Error> { drop(stubr); Ok(()) } + +#[actix_rt::test] +#[stubr::mock(port = 4321)] +async fn anonymous_newsletter_test() -> Result<(), Error> { + let pool = reset()?; + wait_for_stubr().await?; + let app = test_app_with_login(&pool).await.unwrap(); + let service = test::init_service(app).await; + let request = test::TestRequest::post() + .set_json(json!({ "email": "foo@bar.com"})) + .uri("/api/v1/newsletter") + .to_request(); + let newsletter_res = test::call_service(&service, request).await; + + assert!(newsletter_res.status().is_success()); + + drop(stubr); + Ok(()) +} + +#[actix_rt::test] +async fn anonymous_newsletter_error_test() -> Result<(), Error> { + let (_, stubr) = init_test(vec![ + "tests/stubs", + "tests/test_specific_stubs/newsletter/basket_subscribe_error.json", + ]) + .await?; + + let pool = reset()?; + let app = test_app_with_login(&pool).await.unwrap(); + let service = test::init_service(app).await; + let request = test::TestRequest::post() + .set_json(json!({ "email": "foo@bar.com"})) + .uri("/api/v1/newsletter") + .to_request(); + let newsletter_res = test::call_service(&service, request).await; + + assert!(newsletter_res.status().is_server_error()); + + drop(stubr); + Ok(()) +} diff --git a/tests/test_specific_stubs/newsletter/basket_subscribe_error.json b/tests/test_specific_stubs/newsletter/basket_subscribe_error.json new file mode 100644 index 00000000..47e5f7b2 --- /dev/null +++ b/tests/test_specific_stubs/newsletter/basket_subscribe_error.json @@ -0,0 +1,17 @@ +{ + "uuid": "basket_subscribe", + "priority": 1, + "request": { + "method": "POST", + "url": "/news/subscribe/" + }, + "response": { + "status": 500, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "status": "error" + } + } +}