refactor: changes

This commit is contained in:
Sandro Eiler 2024-01-21 22:00:10 +01:00
parent 476eed4559
commit f4deaceb27
7 changed files with 137 additions and 12 deletions

1
.env Normal file
View file

@ -0,0 +1 @@
DATABASE_URL="postgres://postgres:password@localhost:5432/newsletter"

94
Dockerfile Normal file
View 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" ]

View file

@ -4,4 +4,4 @@ database:
port: 5432
username: "postgres"
password: "password"
database_name: "newsletter"
name: "newsletter"

View file

@ -15,13 +15,15 @@ pub struct Settings {
/// * `password`: the DB pasword
/// * `port`: the DB port
/// * `host`: the DB host address
/// * `database_name`: the DB name
/// * `name`: the DB name
pub struct DatabaseSettings {
pub username: String,
pub password: String,
pub port: u16,
pub host: String,
pub database_name: String,
pub name: String,
#[serde(default)]
pub require_ssl: bool,
}
/// Provides the application settings
@ -39,7 +41,7 @@ impl DatabaseSettings {
pub fn connection_string(&self) -> String {
format!(
"postgres://{}:{}@{}:{}/{}",
self.username, self.password, self.host, self.port, self.database_name
self.username, self.password, self.host, self.port, self.name
)
}
}

View file

@ -1,5 +1,8 @@
use std::time::Duration;
use learn_axum::configuration::get_configuration;
use learn_axum::startup;
use sqlx::postgres::PgPoolOptions;
use tokio::net::TcpListener;
#[tokio::main]
@ -7,5 +10,11 @@ async fn main() {
let configuration = get_configuration().expect("Failed to read configuration.");
let addr = format!("127.0.0.1:{}", configuration.application_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();
}

View file

@ -1,16 +1,18 @@
use axum::routing::IntoMakeService;
use axum::serve::Serve;
use axum::Router;
use sqlx::PgPool;
use tokio::net::TcpListener;
/// API routing
pub fn app() -> Router {
pub fn app(connection: PgPool) -> Router {
Router::new()
.with_state(connection)
.merge(crate::routes::routes_health_check())
.merge(crate::routes::routes_subscriptions())
}
/// Start the server
pub fn run(listener: TcpListener) -> Serve<IntoMakeService<Router>, Router> {
axum::serve(listener, app().into_make_service())
pub fn run(listener: TcpListener, connection: PgPool) -> Serve<IntoMakeService<Router>, Router> {
axum::serve(listener, app(connection).into_make_service())
}

View file

@ -1,6 +1,7 @@
use std::time::Duration;
use learn_axum::configuration::get_configuration;
use sqlx::{Connection, PgConnection};
use std::net::SocketAddr;
use sqlx::{Connection, PgConnection, postgres::PgPoolOptions};
use tokio::net::TcpListener;
struct TestApp {
@ -33,7 +34,7 @@ async fn subscribe_returns_a_200_for_valid_form_data() {
let connection_string = configuration.database.connection_string();
// The `Connection` trait MUST be in scope for us to invoke
// `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
.expect("Failed to connect to Postgres.");
let client = reqwest::Client::new();
@ -50,6 +51,14 @@ async fn subscribe_returns_a_200_for_valid_form_data() {
// Assert
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]
@ -87,8 +96,16 @@ async fn spawn_app() -> TestApp {
let listener = TcpListener::bind("127.0.0.1:0").await.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 {
axum::serve(listener, learn_axum::startup::app())
axum::serve(listener, learn_axum::startup::app(pool))
.await
.unwrap();
});