init projectmycelium
This commit is contained in:
532
src/config/builder.rs
Normal file
532
src/config/builder.rs
Normal file
@@ -0,0 +1,532 @@
|
||||
use std::env;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Centralized configuration builder for all environment variables and app settings
|
||||
///
|
||||
/// This builder consolidates all scattered env::var() calls throughout the codebase
|
||||
/// into a single source of truth, following the established builder pattern architecture.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```rust,ignore
|
||||
/// let config = ConfigurationBuilder::new().build();
|
||||
/// if config.is_gitea_enabled() {
|
||||
/// // Handle Gitea flow
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigurationBuilder {
|
||||
// OAuth Configuration
|
||||
gitea_client_id: Option<String>,
|
||||
gitea_client_secret: Option<String>,
|
||||
gitea_instance_url: Option<String>,
|
||||
|
||||
// App Configuration
|
||||
app_url: String,
|
||||
jwt_secret: String,
|
||||
secret_key: Option<String>,
|
||||
app_config_path: Option<String>,
|
||||
/// Whether mock data/services are enabled (dev/test default: true; prod default: false)
|
||||
enable_mock_data: bool,
|
||||
/// Data source for marketplace data (fixtures/mock/live)
|
||||
data_source: DataSource,
|
||||
/// Filesystem path to fixtures (used when data_source=fixtures)
|
||||
fixtures_path: String,
|
||||
/// Whether demo mode UX/guards are enabled
|
||||
demo_mode: bool,
|
||||
/// Whether to enable in-memory catalog cache (dev/test only by default)
|
||||
catalog_cache_enabled: bool,
|
||||
/// TTL for catalog cache in seconds
|
||||
catalog_cache_ttl_secs: u64,
|
||||
|
||||
// Server Configuration
|
||||
environment: AppEnvironment,
|
||||
}
|
||||
|
||||
/// Application environment types
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AppEnvironment {
|
||||
Development,
|
||||
Production,
|
||||
Testing,
|
||||
}
|
||||
|
||||
/// Data source for marketplace data
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum DataSource {
|
||||
/// Legacy in-memory mocks (dev only)
|
||||
Mock,
|
||||
/// Filesystem-backed fixtures under fixtures_path
|
||||
Fixtures,
|
||||
/// Live backend (e.g., PostgREST/DB)
|
||||
Live,
|
||||
}
|
||||
|
||||
/// Built configuration ready for use throughout the application
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppConfiguration {
|
||||
// OAuth Configuration
|
||||
pub gitea_client_id: Option<String>,
|
||||
pub gitea_client_secret: Option<String>,
|
||||
pub gitea_instance_url: Option<String>,
|
||||
|
||||
// App Configuration
|
||||
pub app_url: String,
|
||||
pub jwt_secret: String,
|
||||
pub secret_key: Option<String>,
|
||||
pub app_config_path: Option<String>,
|
||||
/// Whether mock data/services are enabled
|
||||
pub enable_mock_data: bool,
|
||||
/// Selected data source
|
||||
pub data_source: DataSource,
|
||||
/// Fixtures path when using fixtures
|
||||
pub fixtures_path: String,
|
||||
/// Demo mode enabled
|
||||
pub demo_mode: bool,
|
||||
/// Catalog cache enabled
|
||||
pub catalog_cache_enabled: bool,
|
||||
/// Catalog cache TTL in seconds
|
||||
pub catalog_cache_ttl_secs: u64,
|
||||
|
||||
// Server Configuration
|
||||
pub environment: AppEnvironment,
|
||||
}
|
||||
|
||||
impl ConfigurationBuilder {
|
||||
/// Creates a new configuration builder with default values
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
gitea_client_id: None,
|
||||
gitea_client_secret: None,
|
||||
gitea_instance_url: None,
|
||||
app_url: "http://localhost:9999".to_string(),
|
||||
jwt_secret: "your_jwt_secret_key".to_string(),
|
||||
secret_key: None,
|
||||
app_config_path: None,
|
||||
enable_mock_data: true, // default true for development
|
||||
data_source: DataSource::Fixtures,
|
||||
fixtures_path: "./user_data".to_string(),
|
||||
demo_mode: false,
|
||||
catalog_cache_enabled: true,
|
||||
catalog_cache_ttl_secs: 5,
|
||||
environment: AppEnvironment::Development,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a development configuration with sensible defaults
|
||||
pub fn development() -> Self {
|
||||
Self::new()
|
||||
.environment(AppEnvironment::Development)
|
||||
.app_url("http://localhost:9999".to_string())
|
||||
.jwt_secret("dev_jwt_secret_key".to_string())
|
||||
}
|
||||
|
||||
/// Creates a production configuration
|
||||
pub fn production() -> Self {
|
||||
Self::new()
|
||||
.environment(AppEnvironment::Production)
|
||||
.load_from_environment()
|
||||
}
|
||||
|
||||
/// Creates a testing configuration
|
||||
pub fn testing() -> Self {
|
||||
Self::new()
|
||||
.environment(AppEnvironment::Testing)
|
||||
.app_url("http://localhost:8080".to_string())
|
||||
.jwt_secret("test_jwt_secret_key".to_string())
|
||||
}
|
||||
|
||||
/// Loads all configuration from environment variables
|
||||
pub fn load_from_environment(mut self) -> Self {
|
||||
// OAuth Configuration
|
||||
self.gitea_client_id = env::var("GITEA_CLIENT_ID").ok().filter(|s| !s.is_empty());
|
||||
self.gitea_client_secret = env::var("GITEA_CLIENT_SECRET").ok().filter(|s| !s.is_empty());
|
||||
self.gitea_instance_url = env::var("GITEA_INSTANCE_URL").ok().filter(|s| !s.is_empty());
|
||||
|
||||
// App Configuration
|
||||
if let Ok(app_url) = env::var("APP_URL") {
|
||||
self.app_url = app_url;
|
||||
}
|
||||
|
||||
if let Ok(jwt_secret) = env::var("JWT_SECRET") {
|
||||
self.jwt_secret = jwt_secret;
|
||||
}
|
||||
|
||||
self.secret_key = env::var("SECRET_KEY").ok().filter(|s| !s.is_empty());
|
||||
self.app_config_path = env::var("APP_CONFIG").ok().filter(|s| !s.is_empty());
|
||||
|
||||
// Mock data gating (APP_ENABLE_MOCKS)
|
||||
let enable_mocks_env = env::var("APP_ENABLE_MOCKS").ok();
|
||||
if let Some(val) = enable_mocks_env.as_deref() {
|
||||
let v = val.to_lowercase();
|
||||
// Accept common truthy/falsey values
|
||||
self.enable_mock_data = matches!(
|
||||
v.as_str(),
|
||||
"1" | "true" | "yes" | "on"
|
||||
) || (!matches!(v.as_str(), "0" | "false" | "no" | "off") && v == "true");
|
||||
}
|
||||
|
||||
// Catalog cache (APP_CATALOG_CACHE, APP_CATALOG_CACHE_TTL_SECS)
|
||||
let catalog_cache_env = env::var("APP_CATALOG_CACHE").ok();
|
||||
if let Some(val) = catalog_cache_env.as_deref() {
|
||||
let v = val.to_lowercase();
|
||||
self.catalog_cache_enabled = matches!(
|
||||
v.as_str(),
|
||||
"1" | "true" | "yes" | "on"
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(val) = env::var("APP_CATALOG_CACHE_TTL_SECS") {
|
||||
if let Ok(parsed) = val.parse::<u64>() {
|
||||
self.catalog_cache_ttl_secs = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
// (no dev-specific cache flags; use APP_CATALOG_CACHE* across environments)
|
||||
|
||||
// Environment detection
|
||||
if let Ok(env_var) = env::var("APP_ENV") {
|
||||
self.environment = match env_var.to_lowercase().as_str() {
|
||||
"production" | "prod" => AppEnvironment::Production,
|
||||
"testing" | "test" => AppEnvironment::Testing,
|
||||
_ => AppEnvironment::Development,
|
||||
};
|
||||
}
|
||||
|
||||
// Data source selection
|
||||
if let Ok(ds) = env::var("APP_DATA_SOURCE") {
|
||||
self.data_source = match ds.to_lowercase().as_str() {
|
||||
"mock" => DataSource::Mock,
|
||||
"live" => DataSource::Live,
|
||||
_ => DataSource::Fixtures,
|
||||
};
|
||||
} else {
|
||||
// Default by environment
|
||||
self.data_source = match self.environment {
|
||||
AppEnvironment::Production => DataSource::Live,
|
||||
_ => DataSource::Fixtures,
|
||||
};
|
||||
}
|
||||
|
||||
// Fixtures path
|
||||
if let Ok(path) = env::var("APP_FIXTURES_PATH") {
|
||||
if !path.is_empty() {
|
||||
self.fixtures_path = path;
|
||||
}
|
||||
}
|
||||
|
||||
// Demo mode
|
||||
if let Ok(val) = env::var("APP_DEMO_MODE") {
|
||||
let v = val.to_lowercase();
|
||||
self.demo_mode = matches!(v.as_str(), "1" | "true" | "yes" | "on");
|
||||
}
|
||||
|
||||
// If environment is production and APP_ENABLE_MOCKS not explicitly set,
|
||||
// default to false to ensure clean production runtime
|
||||
if self.environment == AppEnvironment::Production && enable_mocks_env.is_none() {
|
||||
self.enable_mock_data = false;
|
||||
}
|
||||
|
||||
// In production, disable catalog cache by default unless explicitly enabled
|
||||
if self.environment == AppEnvironment::Production && catalog_cache_env.is_none() {
|
||||
self.catalog_cache_enabled = false;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
// Builder methods for fluent interface
|
||||
|
||||
pub fn gitea_client_id(mut self, client_id: String) -> Self {
|
||||
self.gitea_client_id = Some(client_id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gitea_client_secret(mut self, client_secret: String) -> Self {
|
||||
self.gitea_client_secret = Some(client_secret);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gitea_instance_url(mut self, instance_url: String) -> Self {
|
||||
self.gitea_instance_url = Some(instance_url);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn app_url(mut self, url: String) -> Self {
|
||||
self.app_url = url;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn jwt_secret(mut self, secret: String) -> Self {
|
||||
self.jwt_secret = secret;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn secret_key(mut self, key: String) -> Self {
|
||||
self.secret_key = Some(key);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn app_config_path(mut self, path: String) -> Self {
|
||||
self.app_config_path = Some(path);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn environment(mut self, env: AppEnvironment) -> Self {
|
||||
self.environment = env;
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the final configuration
|
||||
pub fn build(self) -> AppConfiguration {
|
||||
AppConfiguration {
|
||||
gitea_client_id: self.gitea_client_id,
|
||||
gitea_client_secret: self.gitea_client_secret,
|
||||
gitea_instance_url: self.gitea_instance_url,
|
||||
app_url: self.app_url,
|
||||
jwt_secret: self.jwt_secret,
|
||||
secret_key: self.secret_key,
|
||||
app_config_path: self.app_config_path,
|
||||
enable_mock_data: self.enable_mock_data,
|
||||
data_source: self.data_source,
|
||||
fixtures_path: self.fixtures_path,
|
||||
demo_mode: self.demo_mode,
|
||||
catalog_cache_enabled: self.catalog_cache_enabled,
|
||||
catalog_cache_ttl_secs: self.catalog_cache_ttl_secs,
|
||||
environment: self.environment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppConfiguration {
|
||||
/// Convenience method to check if Gitea OAuth is enabled
|
||||
pub fn is_gitea_enabled(&self) -> bool {
|
||||
self.gitea_client_id.is_some() &&
|
||||
self.gitea_client_secret.is_some() &&
|
||||
self.gitea_instance_url.is_some()
|
||||
}
|
||||
|
||||
/// Gets the Gitea client ID if available
|
||||
pub fn gitea_client_id(&self) -> Option<&str> {
|
||||
self.gitea_client_id.as_deref()
|
||||
}
|
||||
|
||||
/// Gets the Gitea client secret if available
|
||||
pub fn gitea_client_secret(&self) -> Option<&str> {
|
||||
self.gitea_client_secret.as_deref()
|
||||
}
|
||||
|
||||
/// Gets the Gitea instance URL if available
|
||||
pub fn gitea_instance_url(&self) -> Option<&str> {
|
||||
self.gitea_instance_url.as_deref()
|
||||
}
|
||||
|
||||
/// Gets the app URL
|
||||
pub fn app_url(&self) -> &str {
|
||||
&self.app_url
|
||||
}
|
||||
|
||||
/// Gets the JWT secret
|
||||
pub fn jwt_secret(&self) -> &str {
|
||||
&self.jwt_secret
|
||||
}
|
||||
|
||||
/// Gets the secret key if available
|
||||
pub fn secret_key(&self) -> Option<&str> {
|
||||
self.secret_key.as_deref()
|
||||
}
|
||||
|
||||
/// Gets the app config path if available
|
||||
pub fn app_config_path(&self) -> Option<&str> {
|
||||
self.app_config_path.as_deref()
|
||||
}
|
||||
|
||||
/// Returns true if mock data/services are enabled
|
||||
pub fn enable_mock_data(&self) -> bool {
|
||||
self.enable_mock_data
|
||||
}
|
||||
|
||||
/// Returns the configured data source
|
||||
pub fn data_source(&self) -> &DataSource {
|
||||
&self.data_source
|
||||
}
|
||||
|
||||
/// True when using fixtures-backed data
|
||||
pub fn is_fixtures(&self) -> bool {
|
||||
matches!(self.data_source, DataSource::Fixtures)
|
||||
}
|
||||
|
||||
/// True when using live backend
|
||||
pub fn is_live(&self) -> bool {
|
||||
matches!(self.data_source, DataSource::Live)
|
||||
}
|
||||
|
||||
/// Path to fixtures directory
|
||||
pub fn fixtures_path(&self) -> &str {
|
||||
&self.fixtures_path
|
||||
}
|
||||
|
||||
/// Demo mode flag
|
||||
pub fn is_demo_mode(&self) -> bool {
|
||||
self.demo_mode
|
||||
}
|
||||
|
||||
/// Catalog cache enabled flag
|
||||
pub fn is_catalog_cache_enabled(&self) -> bool {
|
||||
self.catalog_cache_enabled
|
||||
}
|
||||
|
||||
/// Catalog cache TTL (seconds)
|
||||
pub fn catalog_cache_ttl_secs(&self) -> u64 {
|
||||
self.catalog_cache_ttl_secs
|
||||
}
|
||||
|
||||
/// Checks if running in development environment
|
||||
pub fn is_development(&self) -> bool {
|
||||
self.environment == AppEnvironment::Development
|
||||
}
|
||||
|
||||
/// Checks if running in production environment
|
||||
pub fn is_production(&self) -> bool {
|
||||
self.environment == AppEnvironment::Production
|
||||
}
|
||||
|
||||
/// Checks if running in testing environment
|
||||
pub fn is_testing(&self) -> bool {
|
||||
self.environment == AppEnvironment::Testing
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigurationBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new().load_from_environment()
|
||||
}
|
||||
}
|
||||
|
||||
/// Global configuration instance - lazy static for single initialization
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static GLOBAL_CONFIG: OnceLock<AppConfiguration> = OnceLock::new();
|
||||
|
||||
/// Gets the global application configuration
|
||||
///
|
||||
/// This function provides a singleton pattern for configuration access,
|
||||
/// ensuring consistent configuration throughout the application lifecycle.
|
||||
pub fn get_app_config() -> &'static AppConfiguration {
|
||||
GLOBAL_CONFIG.get_or_init(|| {
|
||||
ConfigurationBuilder::default().build()
|
||||
})
|
||||
}
|
||||
|
||||
/// Initializes the global configuration with a custom builder
|
||||
///
|
||||
/// This should be called once at application startup if custom configuration is needed.
|
||||
/// If not called, the default environment-based configuration will be used.
|
||||
pub fn init_app_config(config: AppConfiguration) -> Result<(), AppConfiguration> {
|
||||
GLOBAL_CONFIG.set(config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::{OnceLock, Mutex, MutexGuard};
|
||||
use std::env;
|
||||
|
||||
// Serialize env-manipulating tests to avoid races
|
||||
static ENV_MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
fn env_lock() -> MutexGuard<'static, ()> {
|
||||
ENV_MUTEX.get_or_init(|| Mutex::new(())).lock().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_configuration_builder_defaults() {
|
||||
let config = ConfigurationBuilder::new().build();
|
||||
assert_eq!(config.app_url(), "http://localhost:9999");
|
||||
assert_eq!(config.jwt_secret(), "your_jwt_secret_key");
|
||||
assert!(!config.is_gitea_enabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_development_configuration() {
|
||||
let config = ConfigurationBuilder::development().build();
|
||||
assert!(config.is_development());
|
||||
assert_eq!(config.app_url(), "http://localhost:9999");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_production_configuration() {
|
||||
let config = ConfigurationBuilder::production().build();
|
||||
assert!(config.is_production());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gitea_enabled_check() {
|
||||
let config = ConfigurationBuilder::new()
|
||||
.gitea_client_id("test_id".to_string())
|
||||
.gitea_client_secret("test_secret".to_string())
|
||||
.gitea_instance_url("https://gitea.example.com".to_string())
|
||||
.build();
|
||||
|
||||
assert!(config.is_gitea_enabled());
|
||||
assert_eq!(config.gitea_client_id(), Some("test_id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fluent_interface() {
|
||||
let config = ConfigurationBuilder::new()
|
||||
.app_url("https://example.com".to_string())
|
||||
.jwt_secret("custom_secret".to_string())
|
||||
.environment(AppEnvironment::Testing)
|
||||
.build();
|
||||
|
||||
assert_eq!(config.app_url(), "https://example.com");
|
||||
assert_eq!(config.jwt_secret(), "custom_secret");
|
||||
assert!(config.is_testing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_app_enable_mocks_truthy_values() {
|
||||
let _g = env_lock();
|
||||
// Ensure clean slate
|
||||
env::remove_var("APP_ENV");
|
||||
for val in ["1", "true", "yes", "on", "TRUE", "On"] {
|
||||
env::set_var("APP_ENABLE_MOCKS", val);
|
||||
let cfg = ConfigurationBuilder::default().build();
|
||||
assert!(cfg.enable_mock_data(), "APP_ENABLE_MOCKS='{}' should enable mocks", val);
|
||||
}
|
||||
env::remove_var("APP_ENABLE_MOCKS");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_app_enable_mocks_falsey_values() {
|
||||
let _g = env_lock();
|
||||
env::remove_var("APP_ENV");
|
||||
for val in ["0", "false", "no", "off", "FALSE", "Off"] {
|
||||
env::set_var("APP_ENABLE_MOCKS", val);
|
||||
let cfg = ConfigurationBuilder::default().build();
|
||||
assert!(!cfg.enable_mock_data(), "APP_ENABLE_MOCKS='{}' should disable mocks", val);
|
||||
}
|
||||
env::remove_var("APP_ENABLE_MOCKS");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_production_default_disables_mocks_when_unset() {
|
||||
let _g = env_lock();
|
||||
env::set_var("APP_ENV", "production");
|
||||
env::remove_var("APP_ENABLE_MOCKS");
|
||||
let cfg = ConfigurationBuilder::default().build();
|
||||
assert!(cfg.is_production());
|
||||
assert!(!cfg.enable_mock_data(), "Production default should disable mocks when not explicitly set");
|
||||
env::remove_var("APP_ENV");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_development_default_enables_mocks_when_unset() {
|
||||
let _g = env_lock();
|
||||
env::set_var("APP_ENV", "development");
|
||||
env::remove_var("APP_ENABLE_MOCKS");
|
||||
let cfg = ConfigurationBuilder::default().build();
|
||||
assert!(cfg.is_development());
|
||||
assert!(cfg.enable_mock_data(), "Development default should enable mocks when not explicitly set");
|
||||
env::remove_var("APP_ENV");
|
||||
}
|
||||
}
|
72
src/config/mod.rs
Normal file
72
src/config/mod.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use config::{Config, ConfigError, File};
|
||||
use serde::Deserialize;
|
||||
use std::env;
|
||||
|
||||
// Export OAuth module
|
||||
pub mod oauth;
|
||||
|
||||
// Export configuration builder
|
||||
pub mod builder;
|
||||
pub use builder::get_app_config;
|
||||
|
||||
/// Application configuration
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct AppConfig {
|
||||
/// Server configuration
|
||||
pub server: ServerConfig,
|
||||
/// Template configuration
|
||||
pub templates: TemplateConfig,
|
||||
}
|
||||
|
||||
/// Server configuration
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ServerConfig {
|
||||
/// Host address to bind to
|
||||
pub host: String,
|
||||
/// Port to listen on
|
||||
pub port: u16,
|
||||
/// Workers count
|
||||
pub workers: Option<u32>,
|
||||
}
|
||||
|
||||
/// Template configuration
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct TemplateConfig {
|
||||
/// Directory containing templates
|
||||
pub dir: String,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
/// Loads configuration from files and environment variables
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
// Set default values
|
||||
let mut config_builder = Config::builder()
|
||||
.set_default("server.host", "127.0.0.1")?
|
||||
.set_default("server.port", 9999)?
|
||||
.set_default("server.workers", None::<u32>)?
|
||||
.set_default("templates.dir", "./src/views")?;
|
||||
|
||||
// Load from config file if it exists
|
||||
if let Ok(config_path) = env::var("APP_CONFIG") {
|
||||
config_builder = config_builder.add_source(File::with_name(&config_path));
|
||||
} else {
|
||||
// Try to load from default locations
|
||||
config_builder = config_builder
|
||||
.add_source(File::with_name("config/default").required(false))
|
||||
.add_source(File::with_name("config/local").required(false));
|
||||
}
|
||||
|
||||
// Override with environment variables (e.g., SERVER__HOST, SERVER__PORT)
|
||||
config_builder =
|
||||
config_builder.add_source(config::Environment::with_prefix("APP").separator("__"));
|
||||
|
||||
// Build and deserialize the config
|
||||
let config = config_builder.build()?;
|
||||
config.try_deserialize()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the application configuration
|
||||
pub fn get_config() -> AppConfig {
|
||||
AppConfig::new().expect("Failed to load configuration")
|
||||
}
|
65
src/config/oauth.rs
Normal file
65
src/config/oauth.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::config::get_app_config;
|
||||
|
||||
/// Gitea OAuth configuration
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GiteaOAuthConfig {
|
||||
/// OAuth client
|
||||
pub client: BasicClient,
|
||||
/// Gitea instance URL
|
||||
pub instance_url: String,
|
||||
}
|
||||
|
||||
impl GiteaOAuthConfig {
|
||||
/// Creates a new Gitea OAuth configuration
|
||||
pub fn new() -> Self {
|
||||
// Get configuration from centralized ConfigurationBuilder
|
||||
let config = get_app_config();
|
||||
|
||||
let client_id = config.gitea_client_id()
|
||||
.expect("Missing GITEA_CLIENT_ID environment variable").to_string();
|
||||
let client_secret = config.gitea_client_secret()
|
||||
.expect("Missing GITEA_CLIENT_SECRET environment variable").to_string();
|
||||
let instance_url = config.gitea_instance_url()
|
||||
.expect("Missing GITEA_INSTANCE_URL environment variable").to_string();
|
||||
|
||||
// Create OAuth client
|
||||
let auth_url = format!("{}/login/oauth/authorize", instance_url);
|
||||
let token_url = format!("{}/login/oauth/access_token", instance_url);
|
||||
|
||||
let client = BasicClient::new(
|
||||
ClientId::new(client_id),
|
||||
Some(ClientSecret::new(client_secret)),
|
||||
AuthUrl::new(auth_url).unwrap(),
|
||||
Some(TokenUrl::new(token_url).unwrap()),
|
||||
)
|
||||
.set_redirect_uri(
|
||||
RedirectUrl::new(format!(
|
||||
"{}/auth/gitea/callback",
|
||||
config.app_url()
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
Self {
|
||||
client,
|
||||
instance_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gitea user information structure
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct GiteaUser {
|
||||
/// User ID
|
||||
pub id: i64,
|
||||
/// Username
|
||||
pub login: String,
|
||||
/// Full name
|
||||
pub full_name: String,
|
||||
/// Email address
|
||||
pub email: String,
|
||||
/// Avatar URL
|
||||
pub avatar_url: String,
|
||||
}
|
Reference in New Issue
Block a user