refactor: changes
This commit is contained in:
parent
476eed4559
commit
f4deaceb27
7 changed files with 137 additions and 12 deletions
1
.env
Normal file
1
.env
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL="postgres://postgres:password@localhost:5432/newsletter"
|
||||||
94
Dockerfile
Normal file
94
Dockerfile
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Global ARGs
|
||||||
|
|
||||||
|
ARG WORKDIR_ROOT=/usr/src
|
||||||
|
|
||||||
|
ARG PROJECT_NAME=learn_axum
|
||||||
|
|
||||||
|
ARG BUILD_TARGET=x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
ARG BUILD_MODE=release
|
||||||
|
|
||||||
|
ARG BUILD_BIN=${PROJECT_NAME}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
FROM clux/muslrust:stable AS build
|
||||||
|
|
||||||
|
# Import global ARGs
|
||||||
|
ARG WORKDIR_ROOT
|
||||||
|
ARG PROJECT_NAME
|
||||||
|
ARG BUILD_TARGET
|
||||||
|
ARG BUILD_MODE
|
||||||
|
ARG BUILD_BIN
|
||||||
|
|
||||||
|
WORKDIR ${WORKDIR_ROOT}
|
||||||
|
|
||||||
|
# Docker build cache: Create and build an empty dummy project with all
|
||||||
|
# external dependencies to avoid redownloading them on subsequent builds
|
||||||
|
# if unchanged.
|
||||||
|
RUN USER=root \
|
||||||
|
cargo new --bin ${PROJECT_NAME}
|
||||||
|
WORKDIR ${WORKDIR_ROOT}/${PROJECT_NAME}
|
||||||
|
|
||||||
|
COPY [ \
|
||||||
|
"Cargo.toml", \
|
||||||
|
"Cargo.lock", \
|
||||||
|
"./" ]
|
||||||
|
|
||||||
|
# Build the dummy project(s), then delete all build artefacts that must(!) not be cached
|
||||||
|
# RUN cargo build --${BUILD_MODE} --target ${BUILD_TARGET}
|
||||||
|
#\
|
||||||
|
# && \
|
||||||
|
# rm -f ./target/${BUILD_TARGET}/${BUILD_MODE}/${PROJECT_NAME}* \
|
||||||
|
# && \
|
||||||
|
# rm -f ./target/${BUILD_TARGET}/${BUILD_MODE}/deps/${PROJECT_NAME}-* \
|
||||||
|
# && \
|
||||||
|
# rm -rf ./target/${BUILD_TARGET}/${BUILD_MODE}/.fingerprint/${PROJECT_NAME}-*
|
||||||
|
|
||||||
|
# Copy all project (re-)sources that are required for building (ordered alphabetically)
|
||||||
|
COPY [ "migrations", "./migrations/" ]
|
||||||
|
COPY [ "src", "./src/" ]
|
||||||
|
|
||||||
|
# Test and build the actual project
|
||||||
|
RUN cargo test --${BUILD_MODE} --target ${BUILD_TARGET} --workspace \
|
||||||
|
&& \
|
||||||
|
cargo build --${BUILD_MODE} --target ${BUILD_TARGET} --bin ${BUILD_BIN} \
|
||||||
|
&& \
|
||||||
|
strip ./target/${BUILD_TARGET}/${BUILD_MODE}/${BUILD_BIN}
|
||||||
|
|
||||||
|
# Switch back to the root directory
|
||||||
|
#
|
||||||
|
# NOTE(2019-08-30, uklotzde): Otherwise copying from the build image fails
|
||||||
|
# during all subsequent builds of the 2nd stage with an unchanged 1st stage
|
||||||
|
# image. Tested with podman 1.5.x on Fedora 30.
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# 2nd Build Stage
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
# Import global ARGs
|
||||||
|
ARG WORKDIR_ROOT
|
||||||
|
ARG PROJECT_NAME
|
||||||
|
ARG BUILD_TARGET
|
||||||
|
ARG BUILD_MODE
|
||||||
|
ARG BUILD_BIN
|
||||||
|
|
||||||
|
ARG DATA_VOLUME="/volume"
|
||||||
|
|
||||||
|
ARG EXPOSE_PORT=8080
|
||||||
|
|
||||||
|
# Copy the statically-linked executable into the minimal scratch image
|
||||||
|
COPY --from=build [ \
|
||||||
|
"${WORKDIR_ROOT}/${PROJECT_NAME}/target/${BUILD_TARGET}/${BUILD_MODE}/${BUILD_BIN}", \
|
||||||
|
"./entrypoint" ]
|
||||||
|
|
||||||
|
EXPOSE ${EXPOSE_PORT}
|
||||||
|
|
||||||
|
VOLUME [ ${DATA_VOLUME} ]
|
||||||
|
|
||||||
|
# Bind the exposed port to Rocket that is used as the web framework
|
||||||
|
ENV SERVER_PORT ${EXPOSE_PORT}
|
||||||
|
|
||||||
|
ENTRYPOINT [ "./entrypoint" ]
|
||||||
|
|
@ -4,4 +4,4 @@ database:
|
||||||
port: 5432
|
port: 5432
|
||||||
username: "postgres"
|
username: "postgres"
|
||||||
password: "password"
|
password: "password"
|
||||||
database_name: "newsletter"
|
name: "newsletter"
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,15 @@ pub struct Settings {
|
||||||
/// * `password`: the DB pasword
|
/// * `password`: the DB pasword
|
||||||
/// * `port`: the DB port
|
/// * `port`: the DB port
|
||||||
/// * `host`: the DB host address
|
/// * `host`: the DB host address
|
||||||
/// * `database_name`: the DB name
|
/// * `name`: the DB name
|
||||||
pub struct DatabaseSettings {
|
pub struct DatabaseSettings {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub database_name: String,
|
pub name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub require_ssl: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides the application settings
|
/// Provides the application settings
|
||||||
|
|
@ -39,7 +41,7 @@ impl DatabaseSettings {
|
||||||
pub fn connection_string(&self) -> String {
|
pub fn connection_string(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"postgres://{}:{}@{}:{}/{}",
|
"postgres://{}:{}@{}:{}/{}",
|
||||||
self.username, self.password, self.host, self.port, self.database_name
|
self.username, self.password, self.host, self.port, self.name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
src/main.rs
11
src/main.rs
|
|
@ -1,5 +1,8 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use learn_axum::configuration::get_configuration;
|
use learn_axum::configuration::get_configuration;
|
||||||
use learn_axum::startup;
|
use learn_axum::startup;
|
||||||
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -7,5 +10,11 @@ async fn main() {
|
||||||
let configuration = get_configuration().expect("Failed to read configuration.");
|
let configuration = get_configuration().expect("Failed to read configuration.");
|
||||||
let addr = format!("127.0.0.1:{}", configuration.application_port);
|
let addr = format!("127.0.0.1:{}", configuration.application_port);
|
||||||
let listener = TcpListener::bind(addr).await.unwrap(); //.expect("Unable to bind to port");
|
let listener = TcpListener::bind(addr).await.unwrap(); //.expect("Unable to bind to port");
|
||||||
startup::run(listener).await.unwrap();
|
let pool = PgPoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.acquire_timeout(Duration::from_secs(3))
|
||||||
|
.connect(&configuration.database.connection_string())
|
||||||
|
.await
|
||||||
|
.expect("can't connect to database");
|
||||||
|
startup::run(listener, pool).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
use axum::routing::IntoMakeService;
|
use axum::routing::IntoMakeService;
|
||||||
use axum::serve::Serve;
|
use axum::serve::Serve;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
use sqlx::PgPool;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
/// API routing
|
/// API routing
|
||||||
pub fn app() -> Router {
|
pub fn app(connection: PgPool) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
.with_state(connection)
|
||||||
.merge(crate::routes::routes_health_check())
|
.merge(crate::routes::routes_health_check())
|
||||||
.merge(crate::routes::routes_subscriptions())
|
.merge(crate::routes::routes_subscriptions())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the server
|
/// Start the server
|
||||||
pub fn run(listener: TcpListener) -> Serve<IntoMakeService<Router>, Router> {
|
pub fn run(listener: TcpListener, connection: PgPool) -> Serve<IntoMakeService<Router>, Router> {
|
||||||
axum::serve(listener, app().into_make_service())
|
axum::serve(listener, app(connection).into_make_service())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use learn_axum::configuration::get_configuration;
|
use learn_axum::configuration::get_configuration;
|
||||||
use sqlx::{Connection, PgConnection};
|
use sqlx::{Connection, PgConnection, postgres::PgPoolOptions};
|
||||||
use std::net::SocketAddr;
|
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
struct TestApp {
|
struct TestApp {
|
||||||
|
|
@ -33,7 +34,7 @@ async fn subscribe_returns_a_200_for_valid_form_data() {
|
||||||
let connection_string = configuration.database.connection_string();
|
let connection_string = configuration.database.connection_string();
|
||||||
// The `Connection` trait MUST be in scope for us to invoke
|
// The `Connection` trait MUST be in scope for us to invoke
|
||||||
// `PgConnection::connect` - it is not an inherent method of the struct!
|
// `PgConnection::connect` - it is not an inherent method of the struct!
|
||||||
let connection = PgConnection::connect(&connection_string)
|
let mut connection = PgConnection::connect(&connection_string)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect to Postgres.");
|
.expect("Failed to connect to Postgres.");
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
@ -50,6 +51,14 @@ async fn subscribe_returns_a_200_for_valid_form_data() {
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assert_eq!(200, response.status().as_u16());
|
assert_eq!(200, response.status().as_u16());
|
||||||
|
|
||||||
|
let saved = sqlx::query!("SELECT email, name FROM subscriptions",)
|
||||||
|
.fetch_one(&mut connection)
|
||||||
|
.await
|
||||||
|
.expect("Failed to fetch saved subscription.");
|
||||||
|
|
||||||
|
assert_eq!(saved.email, "ursula_le_guin@gmail.com");
|
||||||
|
assert_eq!(saved.name, "le guin");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -87,8 +96,16 @@ async fn spawn_app() -> TestApp {
|
||||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
let address = format!("http://{}", listener.local_addr().unwrap());
|
let address = format!("http://{}", listener.local_addr().unwrap());
|
||||||
|
|
||||||
|
let configuration = get_configuration().expect("Failed to read configuration.");
|
||||||
|
let pool = PgPoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.acquire_timeout(Duration::from_secs(3))
|
||||||
|
.connect(&configuration.database.connection_string())
|
||||||
|
.await
|
||||||
|
.expect("can't connect to database");
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
axum::serve(listener, learn_axum::startup::app())
|
axum::serve(listener, learn_axum::startup::app(pool))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue