When users submit a contact form, they now receive a confirmation email acknowlledging receipt of their message. The backend also continues to send a notification email to the configured recipient. If the backend fails to send the acknowledgement email to the sender, it will assume the email is not valid and will therefore not transmit the contact request to the configured recipient. Changes: - Refactor `send_email()` to `send_emails()` that sends two emails: - Confirmation email from the submitter - Notification email to the configured recipient - Add `From<T>` implementations of various errors for new error type `ContactError`. - Errors now return a translation identifier for the frontend.
85 lines
2.5 KiB
Rust
85 lines
2.5 KiB
Rust
//! Backend API server for phundrak.com
|
|
//!
|
|
//! This is a REST API built with the Poem framework that provides:
|
|
//! - Health check endpoints
|
|
//! - Application metadata endpoints
|
|
//! - Contact form submission with email integration
|
|
|
|
#![deny(clippy::all)]
|
|
#![deny(clippy::pedantic)]
|
|
#![deny(clippy::nursery)]
|
|
#![warn(missing_docs)]
|
|
#![allow(clippy::unused_async)]
|
|
|
|
/// Custom errors
|
|
pub mod errors;
|
|
/// Custom middleware implementations
|
|
pub mod middleware;
|
|
/// API route handlers and endpoints
|
|
pub mod route;
|
|
/// Application configuration settings
|
|
pub mod settings;
|
|
/// Application startup and server configuration
|
|
pub mod startup;
|
|
/// Logging and tracing setup
|
|
pub mod telemetry;
|
|
|
|
type MaybeListener = Option<poem::listener::TcpListener<String>>;
|
|
|
|
fn prepare(listener: MaybeListener) -> startup::Application {
|
|
dotenvy::dotenv().ok();
|
|
let settings = settings::Settings::new().expect("Failed to read settings");
|
|
if !cfg!(test) {
|
|
let subscriber = telemetry::get_subscriber(settings.debug);
|
|
telemetry::init_subscriber(subscriber);
|
|
}
|
|
tracing::event!(
|
|
target: "backend",
|
|
tracing::Level::DEBUG,
|
|
"Using these settings: {:?}",
|
|
settings
|
|
);
|
|
let application = startup::Application::build(settings, listener);
|
|
tracing::event!(
|
|
target: "backend",
|
|
tracing::Level::INFO,
|
|
"Listening on http://{}:{}/",
|
|
application.host(),
|
|
application.port()
|
|
);
|
|
tracing::event!(
|
|
target: "backend",
|
|
tracing::Level::INFO,
|
|
"Documentation available at http://{}:{}/",
|
|
application.host(),
|
|
application.port()
|
|
);
|
|
application
|
|
}
|
|
|
|
/// Runs the application with the specified TCP listener.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns a `std::io::Error` if the server fails to start or encounters
|
|
/// an I/O error during runtime (e.g., port already in use, network issues).
|
|
#[cfg(not(tarpaulin_include))]
|
|
pub async fn run(listener: MaybeListener) -> Result<(), std::io::Error> {
|
|
let application = prepare(listener);
|
|
application.make_app().run().await
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn make_random_tcp_listener() -> poem::listener::TcpListener<String> {
|
|
let tcp_listener =
|
|
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind a random TCP listener");
|
|
let port = tcp_listener.local_addr().unwrap().port();
|
|
poem::listener::TcpListener::bind(format!("127.0.0.1:{port}"))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn get_test_app() -> startup::App {
|
|
let tcp_listener = make_random_tcp_listener();
|
|
prepare(Some(tcp_listener)).make_app().into()
|
|
}
|