zero-to-production/src/routes/subscriptions_confirm.rs
2021-05-12 22:33:43 +01:00

75 lines
2.3 KiB
Rust

use crate::routes::error_chain_fmt;
use actix_web::http::StatusCode;
use actix_web::{web, HttpResponse, ResponseError};
use anyhow::Context;
use sqlx::PgPool;
use uuid::Uuid;
#[derive(serde::Deserialize)]
pub struct Parameters {
subscription_token: String,
}
#[derive(thiserror::Error)]
pub enum ConfirmationError {
#[error(transparent)]
UnexpectedError(#[from] anyhow::Error),
#[error("There is no subscriber associated with the provided token.")]
UnknownToken,
}
impl std::fmt::Debug for ConfirmationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
error_chain_fmt(self, f)
}
}
impl ResponseError for ConfirmationError {
fn status_code(&self) -> StatusCode {
match self {
Self::UnknownToken => StatusCode::UNAUTHORIZED,
Self::UnexpectedError(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
#[tracing::instrument(name = "Confirm a pending subscriber", skip(parameters, pool))]
pub async fn confirm(
parameters: web::Query<Parameters>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ConfirmationError> {
let subscriber_id = get_subscriber_id_from_token(&pool, &parameters.subscription_token)
.await
.context("Failed to retrieve the subscriber id associated with the provided token.")?
.ok_or(ConfirmationError::UnknownToken)?;
confirm_subscriber(&pool, subscriber_id)
.await
.context("Failed to update the subscriber status to `confirmed`.")?;
Ok(HttpResponse::Ok().finish())
}
#[tracing::instrument(name = "Mark subscriber as confirmed", skip(subscriber_id, pool))]
pub async fn confirm_subscriber(pool: &PgPool, subscriber_id: Uuid) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"UPDATE subscriptions SET status = 'confirmed' WHERE id = $1"#,
subscriber_id,
)
.execute(pool)
.await?;
Ok(())
}
#[tracing::instrument(name = "Get subscriber_id from token", skip(subscription_token, pool))]
pub async fn get_subscriber_id_from_token(
pool: &PgPool,
subscription_token: &str,
) -> Result<Option<Uuid>, sqlx::Error> {
let result = sqlx::query!(
r#"SELECT subscriber_id FROM subscription_tokens WHERE subscription_token = $1"#,
subscription_token,
)
.fetch_optional(pool)
.await?;
Ok(result.map(|r| r.subscriber_id))
}