switch to yaml configuration

Signed-off-by: alok8bb <alok8bb@gmail.com>
This commit is contained in:
alok8bb 2023-07-31 10:37:36 +05:30
parent 895b534207
commit 55842e6e3b
No known key found for this signature in database
6 changed files with 89 additions and 38 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target /target
.env .env
/test /test
configuration.yaml

View File

@ -0,0 +1,19 @@
server:
host: 0.0.0.0
port: 7070
db:
user: "oggy"
password: "very_secure_password"
host: "127.0.0.1"
port: "5432"
dbname: "okiba"
pool:
max_size: 16
app:
redis_uri: "redis://127.0.0.1"
rate_limit:
time_seconds: 60
request_count: 10
paste_id_length: 6

View File

@ -3,6 +3,6 @@ CREATE SCHEMA bin;
CREATE TABLE bin.pastes ( CREATE TABLE bin.pastes (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
paste_id VARCHAR(5) NOT NULL, paste_id VARCHAR(6) NOT NULL,
content TEXT NOT NULL content TEXT NOT NULL
) )

View File

@ -1,16 +1,37 @@
pub use config::ConfigError; pub use config::ConfigError;
use config::{File, FileFormat};
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Default, Deserialize)] #[derive(Debug, Default, Deserialize, Clone)]
pub struct AppConfig { pub struct Config {
pub server_addr: String, pub db: deadpool_postgres::Config,
pub pg: deadpool_postgres::Config, pub server: ServerConfig,
pub app: ApplicationConfig,
} }
impl AppConfig { #[derive(Debug, Default, Deserialize, Clone)]
pub struct ServerConfig {
pub port: u16,
pub host: String,
}
#[derive(Debug, Default, Deserialize, Clone)]
pub struct ApplicationConfig {
pub redis_uri: String,
pub rate_limit: RateLimiterConfig,
pub paste_id_length: u8,
}
#[derive(Debug, Default, Deserialize, Clone)]
pub struct RateLimiterConfig {
pub time_seconds: u64,
pub request_count: u64,
}
impl Config {
pub fn from_env() -> Result<Self, ConfigError> { pub fn from_env() -> Result<Self, ConfigError> {
let config = config::Config::builder() let config = config::Config::builder()
.add_source(::config::Environment::default()) .add_source(File::new("configuration", FileFormat::Yaml))
.build()?; .build()?;
config.try_deserialize() config.try_deserialize()
} }

View File

@ -24,7 +24,6 @@ pub mod errors {
#[derive(Display, From, Debug)] #[derive(Display, From, Debug)]
pub enum MyError { pub enum MyError {
NotFound,
PGError(PGError), PGError(PGError),
PGMError(PGMError), PGMError(PGMError),
PoolError(PoolError), PoolError(PoolError),
@ -33,16 +32,19 @@ pub mod errors {
impl ResponseError for MyError { impl ResponseError for MyError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match self {
MyError::NotFound => HttpResponse::NotFound().json(json!({ MyError::PoolError(ref err) => {
"message": "record not found" log::error!("{}", err.to_string());
})), HttpResponse::InternalServerError().json(json!({
MyError::PoolError(ref err) => HttpResponse::InternalServerError().json(json!({ "message": err.to_string()
"message": err.to_string() }))
})), }
_ => HttpResponse::InternalServerError().json(json!({ error => {
"message": "internal server error" log::error!("{}", error.to_string());
})), HttpResponse::InternalServerError().json(json!({
"message": "internal server error"
}))
}
} }
} }
} }

View File

@ -10,16 +10,14 @@ use actix_web::{
web::{self}, web::{self},
App, Error, HttpResponse, HttpServer, App, Error, HttpResponse, HttpServer,
}; };
use cfg::AppConfig; use cfg::ApplicationConfig;
use cfg::Config;
use deadpool_postgres::{Client, Pool}; use deadpool_postgres::{Client, Pool};
use dotenv::dotenv; use dotenv::dotenv;
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng};
use redis::aio::ConnectionManager; use redis::aio::ConnectionManager;
use serde_json::json; use serde_json::json;
use std::{ use std::time::Duration;
str::{self},
time::Duration,
};
use tokio_postgres::NoTls; use tokio_postgres::NoTls;
fn generate_endpoint(length: u8) -> String { fn generate_endpoint(length: u8) -> String {
@ -47,14 +45,18 @@ async fn fetch_paste(
} }
#[post("/paste")] #[post("/paste")]
async fn new_paste(db_pool: web::Data<Pool>, body: String) -> Result<HttpResponse, Error> { async fn new_paste(
db_pool: web::Data<Pool>,
app_config: web::Data<ApplicationConfig>,
body: String,
) -> Result<HttpResponse, Error> {
let client: Client = db_pool.get().await.map_err(MyError::PoolError)?; let client: Client = db_pool.get().await.map_err(MyError::PoolError)?;
let mut endpoint = generate_endpoint(5); let mut endpoint = generate_endpoint(app_config.paste_id_length);
// check if endpoint already exists // check if endpoint already exists
loop { loop {
if db::paste_id_exists(&client, &endpoint).await? { if db::paste_id_exists(&client, &endpoint).await? {
endpoint = generate_endpoint(5) endpoint = generate_endpoint(app_config.paste_id_length)
} else { } else {
break; break;
} }
@ -70,37 +72,43 @@ async fn new_paste(db_pool: web::Data<Pool>, body: String) -> Result<HttpRespons
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
dotenv().ok(); dotenv().ok();
const ADDR: (&str, u16) = ("127.0.0.1", 8080);
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let config = AppConfig::from_env().unwrap(); let config = Config::from_env().unwrap();
log::info!("Connecting database"); log::info!("Connecting database");
let pool = config.pg.create_pool(None, NoTls).unwrap(); let pool = config.db.create_pool(None, NoTls).unwrap();
let redis_client = let redis_client = redis::Client::open(config.app.redis_uri.clone())
redis::Client::open("redis://127.0.0.1:6379").expect("Couldn't connect to redis database"); .expect("Couldn't connect to redis database");
let redis_cm = ConnectionManager::new(redis_client).await.unwrap(); let redis_cm = ConnectionManager::new(redis_client).await.unwrap();
let redis_backend = RedisBackend::builder(redis_cm).build(); let redis_backend = RedisBackend::builder(redis_cm).build();
let server = HttpServer::new(move || { let server = HttpServer::new(move || {
// 5 requests per 60 seconds let input = SimpleInputFunctionBuilder::new(
let input = SimpleInputFunctionBuilder::new(Duration::from_secs(60), 5) Duration::from_secs(config.app.rate_limit.time_seconds),
.real_ip_key() config.app.rate_limit.request_count,
.build(); )
.real_ip_key()
.build();
let middleware = RateLimiter::builder(redis_backend.clone(), input) let middleware = RateLimiter::builder(redis_backend.clone(), input)
.add_headers() .add_headers()
.build(); .build();
App::new() App::new()
.app_data(web::Data::new(pool.clone())) .app_data(web::Data::new(pool.clone()))
.app_data(web::Data::new(config.app.clone()))
.wrap(middleware) .wrap(middleware)
.service(new_paste) .service(new_paste)
.service(fetch_paste) .service(fetch_paste)
}) })
.bind(ADDR)? .bind((&*config.server.host, config.server.port))?
.run(); .run();
log::info!("Starting the server at http://{}:{}", ADDR.0, ADDR.1); log::info!(
"Starting the server at http://{}:{}",
config.server.host,
config.server.port
);
server.await server.await
} }