feat(api): config from env vars (HOST, PORT, CORS_ALLOWED_ORIGINS, DATABASE_URL)
This commit is contained in:
52
crates/api/src/config.rs
Normal file
52
crates/api/src/config.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::env;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub database_url: String,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
/// Parsed CORS origin policy
|
||||
pub cors_origins: CorsOrigins,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CorsOrigins {
|
||||
/// Allow any origin (`CORS_ALLOWED_ORIGINS=*`)
|
||||
Any,
|
||||
/// Allow specific origins (`CORS_ALLOWED_ORIGINS=https://a.com,https://b.com`)
|
||||
List(Vec<String>),
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_env() -> Self {
|
||||
let database_url = env::var("DATABASE_URL")
|
||||
.unwrap_or_else(|_| "sqlite://./pocket-chords.db".into());
|
||||
|
||||
let host = env::var("HOST").unwrap_or_else(|_| "0.0.0.0".into());
|
||||
|
||||
let port = env::var("PORT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u16>().ok())
|
||||
.unwrap_or(8000);
|
||||
|
||||
let cors_origins = match env::var("CORS_ALLOWED_ORIGINS")
|
||||
.unwrap_or_else(|_| "*".into())
|
||||
.trim()
|
||||
.to_string()
|
||||
{
|
||||
s if s == "*" => CorsOrigins::Any,
|
||||
s => CorsOrigins::List(
|
||||
s.split(',')
|
||||
.map(|o| o.trim().to_string())
|
||||
.filter(|o| !o.is_empty())
|
||||
.collect(),
|
||||
),
|
||||
};
|
||||
|
||||
Self { database_url, host, port, cors_origins }
|
||||
}
|
||||
|
||||
pub fn bind_addr(&self) -> String {
|
||||
format!("{}:{}", self.host, self.port)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
mod config;
|
||||
mod routes;
|
||||
|
||||
use axum::{Router, routing::{get, post}};
|
||||
use axum::{Router, http::HeaderValue, routing::{get, post}};
|
||||
use common::{SongSearchService, SongService};
|
||||
use config::{Config, CorsOrigins};
|
||||
use persistence::SqliteRepositoryFactory;
|
||||
use routes::songs::{create_song, delete_song, get_song, list_songs, update_song};
|
||||
use routes::tabs::{AppState, parse_tab};
|
||||
@@ -13,9 +15,10 @@ use ug_parser::{UgHtmlParser, UgTabFetcher};
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let database_url = std::env::var("DATABASE_URL")
|
||||
.unwrap_or_else(|_| "sqlite://./pocket-chords.db".into());
|
||||
let repo = SqliteRepositoryFactory::create(&database_url)
|
||||
let config = Config::from_env();
|
||||
tracing::info!(?config, "starting with config");
|
||||
|
||||
let repo = SqliteRepositoryFactory::create(&config.database_url)
|
||||
.await
|
||||
.expect("failed to connect to database");
|
||||
let songs = SongService::new(Box::new(repo.clone()));
|
||||
@@ -28,10 +31,22 @@ async fn main() {
|
||||
search,
|
||||
});
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any);
|
||||
let cors = match config.cors_origins {
|
||||
CorsOrigins::Any => CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any),
|
||||
CorsOrigins::List(ref origins) => {
|
||||
let parsed: Vec<HeaderValue> = origins
|
||||
.iter()
|
||||
.map(|o| o.parse().unwrap_or_else(|_| panic!("invalid CORS origin: {o}")))
|
||||
.collect();
|
||||
CorsLayer::new()
|
||||
.allow_origin(parsed)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any)
|
||||
}
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/tabs/parse", post(parse_tab))
|
||||
@@ -40,7 +55,9 @@ async fn main() {
|
||||
.layer(cors)
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap();
|
||||
let addr = config.bind_addr();
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await
|
||||
.unwrap_or_else(|e| panic!("failed to bind {addr}: {e}"));
|
||||
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user