Database connection and user registration

This commit is contained in:
Alphonse Paix
2025-08-21 15:38:12 +02:00
parent 1fd1c4eef4
commit ded2a611e2
16 changed files with 3069 additions and 18 deletions

42
src/configuration.rs Normal file
View File

@@ -0,0 +1,42 @@
use serde::Deserialize;
pub fn get_configuration() -> Result<Settings, config::ConfigError> {
let settings = config::Config::builder()
.add_source(config::File::new(
"configuration.yaml",
config::FileFormat::Yaml,
))
.build()?;
settings.try_deserialize::<Settings>()
}
#[derive(Deserialize)]
pub struct Settings {
pub database: DatabaseSettings,
pub application_port: u16,
}
#[derive(Deserialize)]
pub struct DatabaseSettings {
pub username: String,
pub password: String,
pub port: u16,
pub host: String,
pub database_name: String,
}
impl DatabaseSettings {
pub fn connection_string(&self) -> String {
format!(
"postgres://{}:{}@{}:{}/{}",
self.username, self.password, self.host, self.port, self.database_name
)
}
pub fn connection_string_without_db(&self) -> String {
format!(
"postgres://{}:{}@{}:{}",
self.username, self.password, self.host, self.port
)
}
}

3
src/lib.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod configuration;
pub mod routes;
pub mod startup;

View File

@@ -1,9 +1,31 @@
use axum::{Router, routing::get};
use sqlx::PgPool;
use tokio::net::TcpListener;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use zero2prod::{configuration::get_configuration, startup::run};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
format!(
"{}=debug,tower_http=debug,axum::rejection=trace",
env!("CARGO_CRATE_NAME")
)
.into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
let configuration = get_configuration().expect("Failed to read configuration");
let listener = TcpListener::bind(format!("127.0.0.1:{}", configuration.application_port))
.await
.unwrap();
tracing::debug!("listening on {}", listener.local_addr().unwrap());
let connection_pool = PgPool::connect(&configuration.database.connection_string())
.await
.unwrap();
run(listener, connection_pool).await
}

5
src/routes.rs Normal file
View File

@@ -0,0 +1,5 @@
mod health_check;
mod subscriptions;
pub use health_check::*;
pub use subscriptions::*;

View File

@@ -0,0 +1,5 @@
use axum::{http::StatusCode, response::IntoResponse};
pub async fn health_check() -> impl IntoResponse {
StatusCode::OK
}

View File

@@ -0,0 +1,37 @@
use axum::{Form, extract::State, http::StatusCode, response::IntoResponse};
use chrono::Utc;
use serde::Deserialize;
use sqlx::PgPool;
use uuid::Uuid;
pub async fn subscribe(
State(connection): State<PgPool>,
form: Form<FormData>,
) -> impl IntoResponse {
match sqlx::query!(
r#"
insert into subscriptions (id, email, name, subscribed_at)
values ($1, $2, $3, $4);
"#,
Uuid::new_v4(),
form.email,
form.name,
Utc::now()
)
.execute(&connection)
.await
{
Ok(_) => StatusCode::OK,
Err(e) => {
eprintln!("Failed to execute query: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
#[derive(Deserialize)]
#[allow(dead_code)]
pub struct FormData {
name: String,
email: String,
}

36
src/startup.rs Normal file
View File

@@ -0,0 +1,36 @@
use crate::routes::*;
use axum::{
Router,
extract::MatchedPath,
http::Request,
routing::{get, post},
};
use sqlx::PgPool;
use tokio::net::TcpListener;
use tower_http::trace::TraceLayer;
pub async fn run(listener: TcpListener, connection_pool: PgPool) {
axum::serve(listener, app(connection_pool)).await.unwrap();
}
pub fn app(connection_pool: PgPool) -> Router {
Router::new()
.route("/health_check", get(health_check))
.route("/subscriptions", post(subscribe))
.layer(
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
let matched_path = request
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str);
tracing::info_span!(
"http_request",
method = ?request.method(),
matched_path,
some_other_field = tracing::field::Empty,
)
}),
)
.with_state(connection_pool)
}