Skip to content

Commit

Permalink
3.8.5.3 Database Connection
Browse files Browse the repository at this point in the history
Signed-off-by: Anthony M. Bonafide <AnthonyMBonafide@gmail.com>
  • Loading branch information
AnthonyMBonafide committed Mar 22, 2024
1 parent bf07164 commit 9f43209
Show file tree
Hide file tree
Showing 13 changed files with 1,461 additions and 122 deletions.
1,401 changes: 1,309 additions & 92 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ name = "zero2prod"
actix-web = "4"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] }
config = "0.14"

[dependencies.sqlx]
verion = "0.7"
default-features = false
features = ["runtime-tokio-rustls", "macros", "postgres", "uuid","chrono","migrate"]

[dev-dependencies]
reqwest = "0.11"
reqwest = "0.12"
8 changes: 8 additions & 0 deletions configuration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
application_port: 8080
database:
host: "127.0.0.1"
port: 5432
username: "postgres"
password: "password"
database_name: "newsletter"

9 changes: 9 additions & 0 deletions migrations/20240321030443_create_subscription_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Add migration script here

CREATE TABLE subscriptions(
id UUID NOT NULL,
PRIMARY KEY(id),
emil TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
subscribed_at TIMESTAMPTZ NOT NULL
);
44 changes: 44 additions & 0 deletions scripts/init_db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash

set -x
set -eo pipefail

if ! [ -x "$(command -v psql)" ]; then
echo >&2 "Error psql not installed."
exit 1
fi

if ! [ -x "$(command -v sqlx)" ]; then
echo >&2 "Error sqlx not installed."
echo >&2 "Use:"
echo >&2 "cargo install --version="~0.7" sqlx-cli --no-default-features --features rustls,postgres"
echo >&2 "to install it"
exit 1
fi

DB_USER="${POSTGRES_USER:=postgres}"
DB_PASSWORD="${POSTGRES_PASSWORD:=password}"
DB_NAME="${POSTGRES_NAME:=newsletter}"
DB_PORT="${POSTGRES_PORT:=5432}"
DB_HOST="${POSTGRES_HOST:=localhost}"

if [[ -z "${SKIP_DOCKER}" ]]; then
podman run -e POSTGRES_USER=${DB_USER} -e POSTGRES_PASSWORD=${DB_PASSWORD} -e POSTGRES_DB=${DB_NAME} -p "${DB_PORT}":5432 -d postgres postgres -N 1000
fi

export PGPASSWORD="${DB_PASSWORD}"

until psql -h "${DB_HOST}" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q'; do
echo >&2 "Postgress is still unavailable - sleeping"
sleep 1
done

echo >&2 "Postgres is up and running on port ${DB_PORT} - running migrations now!"

DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}
export DATABASE_URL

sqlx database create
sqlx migrate run

echo >&2 "Postgres has been migrated, ready to go!"
29 changes: 29 additions & 0 deletions src/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#[derive(serde::Deserialize)]
pub struct Settings{
pub database: DatabaseSettings,
pub application_port: u16,
}

#[derive(serde::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.password, self.database_name)
}
}


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()
}
31 changes: 5 additions & 26 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,7 @@
use std::net::TcpListener;
pub mod configuration;
pub mod routes;
pub mod startup;

use actix_web::{dev::Server, web, App, HttpResponse, HttpServer};
#[derive(serde::Deserialize, serde::Serialize)]
struct FormData {
name: String,
email: String,
}

async fn health_check() -> HttpResponse {
HttpResponse::Ok().finish()
}

async fn subscribe(_form: web::Form<FormData>) -> HttpResponse {
HttpResponse::Ok().finish()
}

pub fn run(tcp_listener: TcpListener) -> Result<Server, std::io::Error> {
let server = HttpServer::new(|| {
App::new()
.route("/health_check", web::get().to(health_check))
.route("/subscribe", web::post().to(subscribe))
})
.listen(tcp_listener)?
.run();

Ok(server)
}
pub use startup::run;
pub use configuration::get_configuration;
6 changes: 4 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::net::TcpListener;

use zero2prod::run;
use zero2prod::{configuration::get_configuration, run};

#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
let listener = TcpListener::bind("127.0.0.1:8080")?;
let config = get_configuration().expect("Failed to read configuration");
let address = format!("127.0.0.1:{}", config.application_port);
let listener = TcpListener::bind(address)?;
run(listener)?.await
}
7 changes: 7 additions & 0 deletions src/routes/health_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

use actix_web::HttpResponse;
pub async fn health_check() -> HttpResponse {
HttpResponse::Ok().finish()
}


7 changes: 7 additions & 0 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

mod health_check;
mod subscriptions;

pub use health_check::*;
pub use subscriptions::*;

14 changes: 14 additions & 0 deletions src/routes/subscriptions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::net::TcpListener;

Check warning on line 1 in src/routes/subscriptions.rs

View workflow job for this annotation

GitHub Actions / Check (1.76.0)

unused import: `std::net::TcpListener`

Check warning on line 1 in src/routes/subscriptions.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

unused import: `std::net::TcpListener`

Check failure on line 1 in src/routes/subscriptions.rs

View workflow job for this annotation

GitHub Actions / Clippy (stable)

unused import: `std::net::TcpListener`

Check failure on line 1 in src/routes/subscriptions.rs

View workflow job for this annotation

GitHub Actions / Clippy (1.76.0)

unused import: `std::net::TcpListener`

use actix_web::{dev::Server, web, App, HttpResponse, HttpServer};

Check warning on line 3 in src/routes/subscriptions.rs

View workflow job for this annotation

GitHub Actions / Check (1.76.0)

unused imports: `App`, `HttpServer`, `dev::Server`

Check warning on line 3 in src/routes/subscriptions.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

unused imports: `App`, `HttpServer`, `dev::Server`

Check failure on line 3 in src/routes/subscriptions.rs

View workflow job for this annotation

GitHub Actions / Clippy (stable)

unused imports: `App`, `HttpServer`, `dev::Server`

Check failure on line 3 in src/routes/subscriptions.rs

View workflow job for this annotation

GitHub Actions / Clippy (1.76.0)

unused imports: `App`, `HttpServer`, `dev::Server`
#[derive(serde::Deserialize, serde::Serialize)]
pub struct FormData {
name: String,
email: String,
}

pub async fn subscribe(_form: web::Form<FormData>) -> HttpResponse {
HttpResponse::Ok().finish()
}


15 changes: 15 additions & 0 deletions src/startup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use std::net::TcpListener;
use actix_web::{dev::Server, web, App, HttpServer};
use crate::routes::{health_check, subscribe};

pub fn run(tcp_listener: TcpListener) -> Result<Server, std::io::Error> {
let server = HttpServer::new(|| {
App::new()
.route("/health_check", web::get().to(health_check))
.route("/subscribe", web::post().to(subscribe))
})
.listen(tcp_listener)?
.run();

Ok(server)
}
4 changes: 3 additions & 1 deletion tests/health_check.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::net::TcpListener;

use zero2prod::run;

#[tokio::test]
async fn health_check_works() {
let address = spawn_app();
Expand Down Expand Up @@ -62,7 +64,7 @@ async fn subscribe_returns_400_for_missing_form_data() {
fn spawn_app() -> String {
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind port");
let port = listener.local_addr().unwrap().port();
let server = zero2prod::run(listener).expect("Failed to bind address");
let server = run(listener).expect("Failed to bind address");
let _server_run = tokio::spawn(server);
std::mem::drop(_server_run);
format!("http://127.0.0.1:{}", port)
Expand Down

0 comments on commit 9f43209

Please sign in to comment.