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, pool: web::Data, ) -> Result { let subscriber_id = get_subscriber_id_from_token(&pool, ¶meters.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, 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)) }