Database connection and user registration
This commit is contained in:
42
src/configuration.rs
Normal file
42
src/configuration.rs
Normal 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
3
src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod configuration;
|
||||
pub mod routes;
|
||||
pub mod startup;
|
||||
30
src/main.rs
30
src/main.rs
@@ -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
5
src/routes.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod health_check;
|
||||
mod subscriptions;
|
||||
|
||||
pub use health_check::*;
|
||||
pub use subscriptions::*;
|
||||
5
src/routes/health_check.rs
Normal file
5
src/routes/health_check.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use axum::{http::StatusCode, response::IntoResponse};
|
||||
|
||||
pub async fn health_check() -> impl IntoResponse {
|
||||
StatusCode::OK
|
||||
}
|
||||
37
src/routes/subscriptions.rs
Normal file
37
src/routes/subscriptions.rs
Normal 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
36
src/startup.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user