use crate::domain::{NewSubscriber, SubscriberEmail, SubscriberName}; use axum::{Form, extract::State, http::StatusCode, response::IntoResponse}; use chrono::Utc; use serde::Deserialize; use sqlx::PgPool; use uuid::Uuid; #[tracing::instrument( name = "Adding a new subscriber", skip(connection, form), fields( subscriber_email = %form.email, subscriber_name = %form.name ) )] pub async fn subscribe( State(connection): State, Form(form): Form, ) -> impl IntoResponse { let new_subscriber = match form.try_into() { Ok(subscriber) => subscriber, Err(_) => return StatusCode::BAD_REQUEST, }; if insert_subscriber(&connection, &new_subscriber) .await .is_err() { StatusCode::INTERNAL_SERVER_ERROR } else { StatusCode::OK } } #[tracing::instrument( name = "Saving new subscriber details in the database", skip(connection, new_subscriber) )] pub async fn insert_subscriber( connection: &PgPool, new_subscriber: &NewSubscriber, ) -> Result<(), sqlx::Error> { sqlx::query!( r#" INSERT INTO subscriptions (id, email, name, subscribed_at) VALUES ($1, $2, $3, $4); "#, Uuid::new_v4(), new_subscriber.email.as_ref(), new_subscriber.name.as_ref(), Utc::now() ) .execute(connection) .await .map_err(|e| { tracing::error!("Failed to execute query: {:?}", e); e })?; Ok(()) } #[derive(Debug, Deserialize)] #[allow(dead_code)] pub struct FormData { name: String, email: String, } impl TryFrom for NewSubscriber { type Error = String; fn try_from(value: FormData) -> Result { let name = SubscriberName::parse(value.name)?; let email = SubscriberEmail::parse(value.email)?; Ok(Self { name, email }) } }