diff --git a/Cargo.lock b/Cargo.lock index 6c003b5..b0412b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1675,6 +1675,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a86348501c129f3ad50c2f4635a01971f76974cd8a3f335988a0f1581c082765" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "serde_derive" version = "1.0.196" @@ -2818,6 +2828,7 @@ dependencies = [ "reqwest", "secrecy", "serde", + "serde-aux", "sqlx", "time", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 8c7a93b..6eb4196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ config = "0.14.0" once_cell = "1.19.0" secrecy = { version = "0.8.0", features = ["serde"] } serde = { version = "1.0.196", features = ["derive"] } +serde-aux = { version = "4.4.0", default-features = false } sqlx = { version = "0.7.3", features = ["macros", "migrate", "postgres", "time", "runtime-tokio", "tls-native-tls", "uuid"], default-features = false } time = { version = "0.3.34", features = ["macros", "serde"] } tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] } diff --git a/configuration/local.yaml b/configuration/local.yaml index b1f2c73..53d8c27 100644 --- a/configuration/local.yaml +++ b/configuration/local.yaml @@ -1,2 +1,4 @@ application: host: localhost +database: + require_ssl: false diff --git a/configuration/production.yaml b/configuration/production.yaml index 137e9f6..aa64e88 100644 --- a/configuration/production.yaml +++ b/configuration/production.yaml @@ -1,2 +1,4 @@ application: host: ::0 +database: + require_ssl: true diff --git a/spec.yaml b/spec.yaml new file mode 100644 index 0000000..4fe5f57 --- /dev/null +++ b/spec.yaml @@ -0,0 +1,39 @@ +name: zero2prod +region: fra +services: + - name: zero2prod + dockerfile_path: Containerfile + source_dir: . + github: + branch: master + deploy_on_push: true + repo: 0rzech/zero2prod + health_check: + http_path: /health_check + http_port: 8000 + instance_count: 1 + instance_size_slug: basic-xxs + routes: + - path: / +envs: + - key: APP_DATABASE__HOST + scope: RUN_TIME + value: ${newsletter.HOSTNAME} + - key: APP_DATABASE__PORT + scope: RUN_TIME + value: ${newsletter.PORT} + - key: APP_DATABASE__USERNAME + scope: RUN_TIME + value: ${newsletter.USERNAME} + - key: APP_DATABASE__PASSWORD + scope: RUN_TIME + value: ${newsletter.PASSWORD} + - key: APP_DATABASE__DATABASE_NAME + scope: RUN_TIME + value: ${newsletter.DATABASE} +databases: + - engine: PG + name: newsletter + num_nodes: 1 + size: db-s-dev-database + version: "12" diff --git a/src/configuration.rs b/src/configuration.rs index 9ceb419..66b008c 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,5 +1,11 @@ use secrecy::{ExposeSecret, Secret}; use serde::Deserialize; +use serde_aux::field_attributes::deserialize_number_from_string; +use sqlx::{ + postgres::{PgConnectOptions, PgSslMode}, + ConnectOptions, +}; +use tracing_log::log::LevelFilter; #[derive(Deserialize)] pub struct Settings { @@ -10,35 +16,41 @@ pub struct Settings { #[derive(Deserialize)] pub struct ApplicationSettings { pub host: String, + #[serde(deserialize_with = "deserialize_number_from_string")] pub port: u16, } #[derive(Deserialize)] pub struct DatabaseSettings { pub host: String, + #[serde(deserialize_with = "deserialize_number_from_string")] pub port: u16, pub username: String, pub password: Secret, pub database_name: String, + pub require_ssl: bool, } impl DatabaseSettings { - pub fn connection_string(&self) -> Secret { - Secret::new(format!( - "{}/{}", - self.connection_string_without_db().expose_secret(), - self.database_name - )) + pub fn with_db(&self) -> PgConnectOptions { + self.without_db() + .database(&self.database_name) + .log_statements(LevelFilter::Trace) } - pub fn connection_string_without_db(&self) -> Secret { - Secret::new(format!( - "postgres://{}:{}@{}:{}", - self.username, - self.password.expose_secret(), - self.host, - self.port - )) + pub fn without_db(&self) -> PgConnectOptions { + let ssl_mode = if self.require_ssl { + PgSslMode::Require + } else { + PgSslMode::Prefer + }; + + PgConnectOptions::new() + .host(&self.host) + .port(self.port) + .username(&self.username) + .password(self.password.expose_secret()) + .ssl_mode(ssl_mode) } } @@ -57,6 +69,11 @@ pub fn get_configuration() -> Result { let settings = config::Config::builder() .add_source(config::File::from(config_dir.join("base.yaml"))) .add_source(config::File::from(config_dir.join(env_config))) + .add_source( + config::Environment::with_prefix("APP") + .prefix_separator("_") + .separator("__"), + ) .build()?; settings.try_deserialize() diff --git a/src/main.rs b/src/main.rs index 10e5126..78f9e92 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use secrecy::ExposeSecret; use sqlx::PgPool; use tokio::net::TcpListener; use zero2prod::{ @@ -19,8 +18,7 @@ async fn main() -> Result<(), std::io::Error> { .await .expect("Failed to open listener"); - let pool = PgPool::connect_lazy(config.database.connection_string().expose_secret()) - .expect("Failed to connect to Postgres"); + let pool = PgPool::connect_lazy_with(config.database.with_db()); run(listener, pool).await } diff --git a/tests/util/mod.rs b/tests/util/mod.rs index 97d5961..c6c9fa9 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -1,5 +1,4 @@ use once_cell::sync::Lazy; -use secrecy::ExposeSecret; use sqlx::{Connection, Executor, PgConnection, PgPool}; use std::net::SocketAddr; use uuid::Uuid; @@ -54,16 +53,15 @@ pub fn url(addr: SocketAddr, endpoint: &str) -> String { } async fn configure_database(configuration: &DatabaseSettings) -> PgPool { - let mut conn = - PgConnection::connect(configuration.connection_string_without_db().expose_secret()) - .await - .expect("Failed to connect to Postgres"); + let mut conn = PgConnection::connect_with(&configuration.without_db()) + .await + .expect("Failed to connect to Postgres"); conn.execute(format!(r#"CREATE DATABASE "{}";"#, configuration.database_name).as_str()) .await .expect("Failed to create database"); - let pool = PgPool::connect(&configuration.connection_string().expose_secret()) + let pool = PgPool::connect_with(configuration.with_db()) .await .expect("Failed to connect to Postgres");