feat: prepare email client usage
This commit is contained in:
parent
eecab50b55
commit
13db7853bd
12 changed files with 150 additions and 208 deletions
|
|
@ -4,6 +4,8 @@ use sqlx::postgres::PgConnectOptions;
|
|||
use sqlx::postgres::PgSslMode;
|
||||
use sqlx::ConnectOptions;
|
||||
|
||||
use crate::domain::SubscriberEmail;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
/// The setting collection.
|
||||
///
|
||||
|
|
@ -12,6 +14,19 @@ use sqlx::ConnectOptions;
|
|||
pub struct Settings {
|
||||
pub database: DatabaseSettings,
|
||||
pub application: ApplicationSettings,
|
||||
pub email_client: EmailClientSettings,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct EmailClientSettings {
|
||||
pub base_url: String,
|
||||
pub sender_email: String,
|
||||
}
|
||||
|
||||
impl EmailClientSettings {
|
||||
pub fn sender(&self) -> Result<SubscriberEmail, String> {
|
||||
SubscriberEmail::parse(self.sender_email.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use validator::validate_email;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubscriberEmail(String);
|
||||
|
||||
impl SubscriberEmail {
|
||||
|
|
|
|||
29
src/email_client.rs
Normal file
29
src/email_client.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::domain::SubscriberEmail;
|
||||
use reqwest::Client;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EmailClient {
|
||||
http_client: Client,
|
||||
base_url: String,
|
||||
sender: SubscriberEmail,
|
||||
}
|
||||
|
||||
impl EmailClient {
|
||||
pub fn new(base_url: String, sender: SubscriberEmail) -> Self {
|
||||
Self {
|
||||
http_client: Client::new(),
|
||||
base_url,
|
||||
sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_email(
|
||||
&self,
|
||||
recipient: SubscriberEmail,
|
||||
subject: &str,
|
||||
html_content: &str,
|
||||
text_content: &str,
|
||||
) -> Result<(), String> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
pub mod configuration;
|
||||
pub mod domain;
|
||||
pub mod email_client;
|
||||
pub mod routes;
|
||||
pub mod startup;
|
||||
pub mod telemetry;
|
||||
|
|
|
|||
10
src/main.rs
10
src/main.rs
|
|
@ -1,4 +1,5 @@
|
|||
use learn_axum::configuration::get_configuration;
|
||||
use learn_axum::email_client::EmailClient;
|
||||
use learn_axum::startup;
|
||||
use learn_axum::telemetry::{get_subscriber, init_subscriber};
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
|
|
@ -20,5 +21,12 @@ async fn main() {
|
|||
let connection_pool = PgPoolOptions::new()
|
||||
.acquire_timeout(std::time::Duration::from_secs(2))
|
||||
.connect_lazy_with(configuration.database.with_db());
|
||||
startup::run(listener, connection_pool).await.unwrap();
|
||||
let sender_email = configuration
|
||||
.email_client
|
||||
.sender()
|
||||
.expect("Invalid sender email address.");
|
||||
let email_client = EmailClient::new(configuration.email_client.base_url, sender_email);
|
||||
startup::run(listener, connection_pool, email_client)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use crate::domain::SubscriberEmail;
|
||||
use crate::domain::{NewSubscriber, SubscriberName};
|
||||
use crate::email_client;
|
||||
use crate::startup::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::routing::post;
|
||||
use axum::Form;
|
||||
|
|
@ -31,21 +33,27 @@ impl TryFrom<FormData> for NewSubscriber {
|
|||
|
||||
#[tracing::instrument(
|
||||
name = "Adding a new subscriber",
|
||||
skip(form, pool),
|
||||
skip(form, db_pool, email_client),
|
||||
fields(
|
||||
request_id = %Uuid::new_v4(),
|
||||
subscriber_email = %form.email,
|
||||
subscriber_name = %form.name
|
||||
)
|
||||
)]
|
||||
pub async fn subscribe(State(pool): State<PgPool>, Form(form): Form<FormData>) -> Response {
|
||||
pub async fn subscribe(
|
||||
State(AppState {
|
||||
db_pool,
|
||||
email_client,
|
||||
}): State<AppState>,
|
||||
Form(form): Form<FormData>,
|
||||
) -> Response {
|
||||
let new_subscriber = match form.try_into() {
|
||||
Ok(form) => form,
|
||||
Err(_) => {
|
||||
return (StatusCode::BAD_REQUEST, "Invalid subscription.").into_response();
|
||||
}
|
||||
};
|
||||
match insert_subscriber(&pool, &new_subscriber).await {
|
||||
match insert_subscriber(&db_pool, &new_subscriber).await {
|
||||
Ok(_) => {
|
||||
return (StatusCode::OK,).into_response();
|
||||
}
|
||||
|
|
@ -57,10 +65,10 @@ pub async fn subscribe(State(pool): State<PgPool>, Form(form): Form<FormData>) -
|
|||
|
||||
#[tracing::instrument(
|
||||
name = "Saving new subscriber details in the database",
|
||||
skip(new_subscriber, pool)
|
||||
skip(new_subscriber, db_pool)
|
||||
)]
|
||||
pub async fn insert_subscriber(
|
||||
pool: &PgPool,
|
||||
db_pool: &PgPool,
|
||||
new_subscriber: &NewSubscriber,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let _ = sqlx::query!(
|
||||
|
|
@ -75,7 +83,7 @@ pub async fn insert_subscriber(
|
|||
)
|
||||
// We use `get_ref` to get an immutable reference to the `PgConnection`
|
||||
// wrapped by `web::Data`.
|
||||
.execute(pool)
|
||||
.execute(db_pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to execute query: {:?}", e);
|
||||
|
|
@ -84,8 +92,8 @@ pub async fn insert_subscriber(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn routes_subscriptions(pool: PgPool) -> Router {
|
||||
pub fn routes_subscriptions(state: AppState) -> Router {
|
||||
Router::new()
|
||||
.route("/subscriptions", post(subscribe))
|
||||
.with_state(pool)
|
||||
.with_state(state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::email_client::EmailClient;
|
||||
use axum::http::Request;
|
||||
use axum::routing::IntoMakeService;
|
||||
use axum::serve::Serve;
|
||||
|
|
@ -13,6 +14,12 @@ use tower_http::{
|
|||
use tracing::Level;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db_pool: PgPool,
|
||||
pub email_client: EmailClient,
|
||||
}
|
||||
|
||||
// from https://docs.rs/tower-http/0.2.5/tower_http/request_id/index.html#using-uuids
|
||||
#[derive(Clone)]
|
||||
struct MakeRequestUuid;
|
||||
|
|
@ -28,10 +35,14 @@ impl MakeRequestId for MakeRequestUuid {
|
|||
/// API routing
|
||||
///
|
||||
/// * `connection`: The postgres connection pool
|
||||
pub fn app(connection: PgPool) -> Router {
|
||||
pub fn app(db_connection: PgPool, email_client: EmailClient) -> Router {
|
||||
let state = AppState {
|
||||
db_pool: db_connection.clone(),
|
||||
email_client: email_client.clone(),
|
||||
};
|
||||
Router::new()
|
||||
.merge(crate::routes::routes_health_check())
|
||||
.merge(crate::routes::routes_subscriptions(connection.clone()))
|
||||
.merge(crate::routes::routes_subscriptions(state))
|
||||
.layer(
|
||||
// from https://docs.rs/tower-http/0.2.5/tower_http/request_id/index.html#using-trace
|
||||
ServiceBuilder::new()
|
||||
|
|
@ -53,6 +64,11 @@ pub fn app(connection: PgPool) -> Router {
|
|||
///
|
||||
/// * `listener`: The TCP listener
|
||||
/// * `connection`: The postgres connection pool
|
||||
pub fn run(listener: TcpListener, connection: PgPool) -> Serve<IntoMakeService<Router>, Router> {
|
||||
axum::serve(listener, app(connection).into_make_service())
|
||||
/// * `email_client`: The email client
|
||||
pub fn run(
|
||||
listener: TcpListener,
|
||||
connection: PgPool,
|
||||
email_client: EmailClient,
|
||||
) -> Serve<IntoMakeService<Router>, Router> {
|
||||
axum::serve(listener, app(connection, email_client).into_make_service())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue