implement StoreTokenError
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse, ResponseError};
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@ -10,6 +10,17 @@ use lettre::{
|
|||||||
|
|
||||||
use crate::{domain::{NewSubscriber, SubscriberEmail, SubscriberName}, email_client::EmailClient, startup::ApplicationBaseUrl};
|
use crate::{domain::{NewSubscriber, SubscriberEmail, SubscriberName}, email_client::EmailClient, startup::ApplicationBaseUrl};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StoreTokenError(sqlx::Error);
|
||||||
|
|
||||||
|
impl std::fmt::Display for StoreTokenError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "A database error was encountered while trying to store the subscription token.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for StoreTokenError {}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct FormData {
|
pub struct FormData {
|
||||||
pub email: String,
|
pub email: String,
|
||||||
@ -29,23 +40,21 @@ pub async fn subscribe(
|
|||||||
connection_pool: web::Data<Pool<Postgres>>,
|
connection_pool: web::Data<Pool<Postgres>>,
|
||||||
email_client: web::Data<EmailClient>,
|
email_client: web::Data<EmailClient>,
|
||||||
base_url: web::Data<ApplicationBaseUrl>,
|
base_url: web::Data<ApplicationBaseUrl>,
|
||||||
) -> HttpResponse {
|
) -> Result<HttpResponse, actix_web::Error> {
|
||||||
let new_subscriber = match form.0.try_into() {
|
let new_subscriber = match form.0.try_into() {
|
||||||
Ok(subscriber) => subscriber,
|
Ok(subscriber) => subscriber,
|
||||||
Err(_) => return HttpResponse::BadRequest().finish(),
|
Err(e) => return Err(actix_web::error::ErrorBadRequest(e)),
|
||||||
};
|
};
|
||||||
let mut transaction = match connection_pool.begin().await {
|
let mut transaction = match connection_pool.begin().await {
|
||||||
Ok(transaction) => transaction,
|
Ok(transaction) => transaction,
|
||||||
Err(_) => return HttpResponse::InternalServerError().finish(),
|
Err(_) => return Err(actix_web::error::ErrorInternalServerError("Failed to acquire a database connection.")),
|
||||||
};
|
};
|
||||||
let subscriber_id = match insert_subscriber(&new_subscriber, &mut transaction).await {
|
let subscriber_id = match insert_subscriber(&new_subscriber, &mut transaction).await {
|
||||||
Ok(subscriber_id) => subscriber_id,
|
Ok(subscriber_id) => subscriber_id,
|
||||||
Err(_) => return HttpResponse::InternalServerError().finish(),
|
Err(_) => return Err(actix_web::error::ErrorInternalServerError("Failed to save new subscriber details.")),
|
||||||
};
|
};
|
||||||
let subscription_token = generate_confirmation_token();
|
let subscription_token = generate_confirmation_token();
|
||||||
if store_token(&mut transaction, &subscriber_id, &subscription_token).await.is_err() {
|
store_token(&mut transaction, &subscriber_id, &subscription_token).await?;
|
||||||
return HttpResponse::InternalServerError().finish();
|
|
||||||
}
|
|
||||||
if transaction.commit().await.is_err() {
|
if transaction.commit().await.is_err() {
|
||||||
return HttpResponse::InternalServerError().finish();
|
return HttpResponse::InternalServerError().finish();
|
||||||
}
|
}
|
||||||
@ -173,7 +182,7 @@ async fn insert_subscriber(new_subscriber: &NewSubscriber, transaction: &mut Tra
|
|||||||
name = "Storing subscription token in the database",
|
name = "Storing subscription token in the database",
|
||||||
skip(transaction, subscriber_id, subscription_token)
|
skip(transaction, subscriber_id, subscription_token)
|
||||||
)]
|
)]
|
||||||
async fn store_token(transaction: &mut Transaction<'_, Postgres>, subscriber_id: &Uuid, subscription_token: &str) -> Result<(), sqlx::Error> {
|
async fn store_token(transaction: &mut Transaction<'_, Postgres>, subscriber_id: &Uuid, subscription_token: &str) -> Result<(), StoreTokenError> {
|
||||||
query!(
|
query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO subscription_tokens (subscription_token, subscriber_id)
|
INSERT INTO subscription_tokens (subscription_token, subscriber_id)
|
||||||
@ -186,7 +195,7 @@ async fn store_token(transaction: &mut Transaction<'_, Postgres>, subscriber_id:
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Failed to execute query: {:?}", e);
|
tracing::error!("Failed to execute query: {:?}", e);
|
||||||
e
|
StoreTokenError(e)
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,4 +118,35 @@ async fn subscribe_sends_a_confirmation_email_with_a_link() {
|
|||||||
|
|
||||||
// The two links should be identical
|
// The two links should be identical
|
||||||
assert_eq!(confirmation_links.html, confirmation_links.plain_text);
|
assert_eq!(confirmation_links.html, confirmation_links.plain_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn subscribe_fails_if_there_is_a_fatal_database_error() {
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let body = "name=Andre%20Heber&email=andre.heber%40gmx.net";
|
||||||
|
|
||||||
|
// Mock::given(path("/email"))
|
||||||
|
// .and(method(Method::POST))
|
||||||
|
// .respond_with(ResponseTemplate::new(200))
|
||||||
|
// .expect(1)
|
||||||
|
// .mount(&app.email_server)
|
||||||
|
// .await;
|
||||||
|
|
||||||
|
// // Let's cause a database error
|
||||||
|
// let (subscribers, pool) = app.get_subscribers().await;
|
||||||
|
sqlx::query!("ALTER TABLE subscription_tokens DROP COLUMN subscription_token;",)
|
||||||
|
.execute(&app.connection_pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to drop the subscriptions table");
|
||||||
|
|
||||||
|
let response = app.post_subscriptions(body.to_string()).await;
|
||||||
|
assert_eq!(response.status().as_u16(), 500);
|
||||||
|
|
||||||
|
// Bring the table back for other tests
|
||||||
|
// sqlx::query(include_str!("../../../migrations/redo_subscriptions_table.sql"))
|
||||||
|
// .execute(&pool)
|
||||||
|
// .await
|
||||||
|
// .expect("Failed to re-create the subscriptions table");
|
||||||
|
|
||||||
|
// drop(subscribers);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user