configuration refactoring [wip]

This commit is contained in:
2024-03-28 17:07:59 +01:00
parent c1615a1bcb
commit 2db4972394
13 changed files with 802 additions and 68 deletions
+118
View File
@@ -0,0 +1,118 @@
use config::{Config, ConfigError};
use secrecy::{ExposeSecret, Secret};
#[derive(serde::Deserialize, Debug)]
pub struct Settings {
pub database: DatabaseSettings,
pub application: ApplicationSettings,
}
#[derive(serde::Deserialize, Debug)]
pub struct ApplicationSettings {
pub port: u16,
pub host: String,
}
#[derive(serde::Deserialize, Debug)]
pub struct DatabaseSettings {
pub username: String,
pub password: Secret<String>,
pub port: u16,
pub host: String,
pub database_name: String,
}
impl TryFrom<Config> for Settings {
type Error = ConfigError;
fn try_from(builder: config::Config) -> Result<Self, Self::Error> {
// Extract values from the builder and construct Settings
let database = builder.get::<DatabaseSettings>("database")?;
let application = builder.get::<ApplicationSettings>("application")?;
Ok(Settings {
database,
application,
})
}
}
impl DatabaseSettings {
pub fn connection_string(&self) -> Secret<String> {
Secret::new(format!(
"postgres://{}:{}@{}:{}/{}",
self.username,
self.password.expose_secret(),
self.host,
self.port,
self.database_name
))
}
pub fn connection_string_without_db(&self) -> Secret<String> {
Secret::new(format!(
"postgres://{}:{}@{}:{}",
self.username,
self.password.expose_secret(),
self.host,
self.port
))
}
}
pub fn get_configuration() -> Result<Settings, ConfigError> {
let base_path = std::env::current_dir().expect("Failed to determine the current directory.");
let configuration_directory = base_path.join("configuration");
// Detect the running environment
// Default to `local`
let environment: Environment = std::env::var("APP_ENVIRONMENT")
.unwrap_or_else(|_| "local".into())
.try_into()
.expect("Failed to parse APP_ENVIRONMENT.");
let environment_filename = format!("{}.yaml", environment.as_str());
// Initialise our configuration reader
let settings = config::Config::builder()
// Add configuration values from a file named `configuration.yaml`.
.add_source(config::File::from(
configuration_directory.join("base.yaml"),
))
.add_source(config::File::from(
configuration_directory.join(environment_filename),
))
.build()?;
// Try to convert the configuration values it read into
// our Settings type
settings.try_deserialize::<Settings>()
}
pub enum Environment {
Local,
Production,
}
impl Environment {
pub fn as_str(&self) -> &'static str {
match self {
Environment::Local => "local",
Environment::Production => "production",
}
}
}
impl TryFrom<String> for Environment {
type Error = String;
fn try_from(s: String) -> Result<Self, Self::Error> {
match s.to_lowercase().as_str() {
"local" => Ok(Self::Local),
"production" => Ok(Self::Production),
other => Err(format!(
"{} is not a supported environement. \
Use either 'local' or 'production'.",
other
)),
}
}
}
+9
View File
@@ -0,0 +1,9 @@
application:
port: 8000
host: 127.0.0.1
database:
host: "127.0.0.1"
port: 5432
username: "postgres"
password: "password"
database_name: "newsletter"
+11
View File
@@ -1,5 +1,6 @@
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use dotenv::dotenv;
use std::env;
@@ -10,3 +11,13 @@ pub fn establish_connection() -> PgConnection {
PgConnection::establish(&database_url)
.unwrap_or_else(|e| panic!("Error connecting to database {}: {}", database_url, e))
}
pub fn get_connection_pool(url: &str) -> Pool<ConnectionManager<PgConnection>> {
let manager = ConnectionManager::<PgConnection>::new(url);
// Refer to the `r2d2` documentation for more methods to use
// when building a connection pool
Pool::builder()
.test_on_check_out(true)
.build(manager)
.expect("Could not build connection pool")
}
+12
View File
@@ -0,0 +1,12 @@
extern crate diesel;
extern crate dotenv;
pub mod auth;
pub mod configuration;
pub mod database;
pub mod json_serialization;
pub mod models;
pub mod reader;
pub mod schema;
pub mod startup;
pub mod views;
+16 -51
View File
@@ -1,61 +1,26 @@
extern crate diesel;
extern crate dotenv;
use std::net::TcpListener;
use actix_service::Service;
use actix_web::{App, HttpResponse, HttpServer};
use futures::future::{ok, Either};
mod auth;
mod database;
mod json_serialization;
mod models;
mod reader;
mod schema;
mod views;
use diesel::{
r2d2::{ConnectionManager, Pool},
PgConnection,
};
use rss_reader::{configuration::get_configuration, database::get_connection_pool, startup::run};
use secrecy::ExposeSecret;
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
HttpServer::new(|| {
let app = App::new()
.wrap_fn(|req, srv| {
let mut passed: bool;
let request_url: String = String::from(req.uri().path());
let configuration = get_configuration().expect("Failed to read configuration.");
log::info!("Request Url: {}", request_url);
if req.path().contains("/article/") {
match auth::process_token(&req) {
Ok(_token) => passed = true,
Err(_message) => passed = false,
}
} else {
log::warn!("No auth check done.");
passed = true;
}
let connection_pool: Pool<ConnectionManager<PgConnection>> =
get_connection_pool(configuration.database.connection_string().expose_secret());
if req.path().contains("user/create") {
passed = true;
}
let address = format!(
"{}:{}",
configuration.application.host, configuration.application.port
);
log::info!("passed: {:?}", passed);
let end_result = match passed {
true => Either::Left(srv.call(req)),
false => Either::Right(ok(req.into_response(
HttpResponse::Unauthorized().finish().map_into_boxed_body(),
))),
};
async move {
let result = end_result.await?;
log::info!("{} -> {}", request_url, &result.status());
Ok(result)
}
})
.configure(views::views_factory);
app
})
.bind("127.0.0.1:8001")?
.run()
.await
let listener = TcpListener::bind(address)?;
run(listener, connection_pool)?.await
}
+10 -3
View File
@@ -4,7 +4,6 @@ use crate::models::feed_item::rss_feed_item::FeedItem;
use crate::reader::structs::feed::FeedAggregate;
use crate::schema::feed_item::{feed_id, id, read};
use crate::{
database::establish_connection,
json_serialization::articles::Articles,
schema::feed::{self, user_id},
schema::feed_item,
@@ -12,15 +11,23 @@ use crate::{
use actix_web::{web, HttpRequest, Responder};
use chrono::Local;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use super::structs::article::Article;
pub async fn get(path: web::Path<JsonUser>, req: HttpRequest) -> impl Responder {
pub async fn get(
path: web::Path<JsonUser>,
req: HttpRequest,
pool: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> impl Responder {
let request = req.clone();
let req_user_id = path.user_id;
log::info!("Received user_id: {}", req_user_id);
// Clone the Arc containing the connection pool
let pool_arc = pool.get_ref().clone();
// Acquire a connection from the pool
let mut connection = pool_arc.get().expect("Failed to get database connection");
let mut connection: diesel::PgConnection = establish_connection();
let feeds: Vec<Feed> = feed::table
.filter(user_id.eq(req_user_id))
.load::<Feed>(&mut connection)
+62
View File
@@ -0,0 +1,62 @@
use std::net::TcpListener;
use actix_service::Service;
use actix_web::web;
use actix_web::{dev::Server, App, HttpResponse, HttpServer};
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection;
use futures::future::{ok, Either};
use crate::auth;
use crate::views;
pub fn run(
listener: TcpListener,
connection: Pool<ConnectionManager<PgConnection>>,
) -> Result<Server, std::io::Error> {
let wrapper = web::Data::new(connection);
let server = HttpServer::new(move || {
let app = App::new()
.wrap_fn(|req, srv| {
let mut passed: bool;
let request_url: String = String::from(req.uri().path());
log::info!("Request Url: {}", request_url);
if req.path().contains("/article/") {
match auth::process_token(&req) {
Ok(_token) => passed = true,
Err(_message) => passed = false,
}
} else {
log::warn!("No auth check done.");
passed = true;
}
if req.path().contains("user/create") {
passed = true;
}
log::info!("passed: {:?}", passed);
let end_result = match passed {
true => Either::Left(srv.call(req)),
false => Either::Right(ok(req.into_response(
HttpResponse::Unauthorized().finish().map_into_boxed_body(),
))),
};
async move {
let result = end_result.await?;
log::info!("{} -> {}", request_url, &result.status());
Ok(result)
}
})
.app_data(wrapper.clone())
.configure(views::views_factory);
app
})
.listen(listener)?
.run();
Ok(server)
}