Initial commit
This commit is contained in:
601
backend/app/core/settings_registry.py
Normal file
601
backend/app/core/settings_registry.py
Normal file
@@ -0,0 +1,601 @@
|
||||
"""
|
||||
Settings Registry - Central definition of all application settings.
|
||||
|
||||
This file defines ALL configurable settings in the application with:
|
||||
- Storage location (database in /config, localStorage, env)
|
||||
- Scope (global or user-specific)
|
||||
- Default values
|
||||
- Data types
|
||||
- Descriptions
|
||||
|
||||
IMPORTANT: Any new setting MUST be added here first.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class SettingScope(Enum):
|
||||
"""Defines who the setting applies to."""
|
||||
GLOBAL = "global" # Same for all users, admin-controlled
|
||||
USER_SPECIFIC = "user" # Each user can have different value
|
||||
SYSTEM = "system" # Infrastructure/env settings, not in DB
|
||||
|
||||
|
||||
class SettingStorage(Enum):
|
||||
"""Defines where the setting is stored."""
|
||||
DATABASE = "database" # Stored in /config/config.db (settings table)
|
||||
LOCAL_STORAGE = "localStorage" # Browser localStorage (frontend only)
|
||||
ENV = "env" # Environment variable (.env file)
|
||||
MEMORY = "memory" # Runtime only, not persisted
|
||||
|
||||
|
||||
class SettingType(Enum):
|
||||
"""Data type of the setting value."""
|
||||
BOOLEAN = "boolean"
|
||||
STRING = "string"
|
||||
INTEGER = "integer"
|
||||
JSON = "json"
|
||||
LIST = "list"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SettingDefinition:
|
||||
"""Definition of a single setting."""
|
||||
key: str
|
||||
type: SettingType
|
||||
scope: SettingScope
|
||||
storage: SettingStorage
|
||||
default: Any
|
||||
description: str
|
||||
category: str
|
||||
admin_only: bool = True # Only admins can modify (for DB settings)
|
||||
sync_to_frontend: bool = True # Should be synced to frontend
|
||||
choices: Optional[list] = None # Valid values if restricted
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SETTINGS REGISTRY
|
||||
# =============================================================================
|
||||
|
||||
SETTINGS_REGISTRY: dict[str, SettingDefinition] = {}
|
||||
|
||||
|
||||
def register_setting(setting: SettingDefinition) -> SettingDefinition:
|
||||
"""Register a setting in the registry."""
|
||||
SETTINGS_REGISTRY[setting.key] = setting
|
||||
return setting
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# THEME SETTINGS (Global, Database)
|
||||
# =============================================================================
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_accent_color",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="auto",
|
||||
description="Accent color for UI elements",
|
||||
category="theme",
|
||||
choices=["auto", "blue", "purple", "green", "orange", "pink", "red", "teal", "amber", "indigo", "cyan", "rose"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_border_radius",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="large",
|
||||
description="Border radius style for UI components",
|
||||
category="theme",
|
||||
choices=["small", "medium", "large"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_sidebar_style",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="default",
|
||||
description="Visual style of the sidebar",
|
||||
category="theme",
|
||||
choices=["default", "dark", "light"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_density",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="compact",
|
||||
description="UI density/spacing",
|
||||
category="theme",
|
||||
choices=["compact", "comfortable", "spacious"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_font_family",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="sans",
|
||||
description="Font family for the application",
|
||||
category="theme",
|
||||
choices=["sans", "inter", "roboto"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_color_palette",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="monochrome",
|
||||
description="Color palette preset",
|
||||
category="theme",
|
||||
choices=["default", "monochrome", "monochromeBlue", "sepia", "nord", "dracula", "solarized", "github", "ocean", "forest", "midnight", "sunset"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_custom_colors",
|
||||
type=SettingType.JSON,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="{}",
|
||||
description="Custom color overrides as JSON",
|
||||
category="theme"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_dark_mode_location",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="sidebar",
|
||||
description="Where to show dark mode toggle",
|
||||
category="theme",
|
||||
choices=["sidebar", "user_menu"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_language_location",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="sidebar",
|
||||
description="Where to show language toggle",
|
||||
category="theme",
|
||||
choices=["sidebar", "user_menu"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_show_dark_mode_toggle",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Show/hide dark mode toggle globally",
|
||||
category="theme"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_show_language_toggle",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=False,
|
||||
description="Show/hide language toggle globally",
|
||||
category="theme"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_show_dark_mode_login",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Show dark mode toggle on login page",
|
||||
category="theme"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="theme_show_language_login",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=False,
|
||||
description="Show language toggle on login page",
|
||||
category="theme"
|
||||
))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MODULE/FEATURE SETTINGS (Global, Database)
|
||||
# =============================================================================
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="module_feature1_admin_enabled",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Enable Feature 1 module for admin users",
|
||||
category="modules"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="module_feature1_user_enabled",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Enable Feature 1 module for regular users",
|
||||
category="modules"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="module_feature2_admin_enabled",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Enable Feature 2 module for admin users",
|
||||
category="modules"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="module_feature2_user_enabled",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Enable Feature 2 module for regular users",
|
||||
category="modules"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="module_feature3_admin_enabled",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Enable Feature 3 module for admin users",
|
||||
category="modules"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="module_feature3_user_enabled",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Enable Feature 3 module for regular users",
|
||||
category="modules"
|
||||
))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# AUTHENTICATION & SECURITY SETTINGS (Global, Database)
|
||||
# =============================================================================
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="registration_enabled",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Allow new user registration",
|
||||
category="auth"
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="user_mode_enabled",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=False,
|
||||
description="Enable admin/user view mode switching",
|
||||
category="auth"
|
||||
))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# UI/LAYOUT SETTINGS (Global, Database)
|
||||
# =============================================================================
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="sidebar_mode",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="toggle",
|
||||
description="Sidebar behavior mode",
|
||||
category="ui",
|
||||
choices=["collapsed", "expanded", "toggle", "dynamic"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="show_logo",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.GLOBAL,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=True,
|
||||
description="Show logo in sidebar instead of text",
|
||||
category="ui"
|
||||
))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# USER-SPECIFIC SETTINGS (Per-user, Database)
|
||||
# These settings can be different for each user
|
||||
# =============================================================================
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="user_theme_mode",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.USER_SPECIFIC,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="system",
|
||||
description="User's preferred theme mode (light/dark/system)",
|
||||
category="user_preferences",
|
||||
admin_only=False,
|
||||
choices=["light", "dark", "system"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="user_language",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.USER_SPECIFIC,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="en",
|
||||
description="User's preferred language",
|
||||
category="user_preferences",
|
||||
admin_only=False,
|
||||
choices=["en", "it"]
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="user_sidebar_collapsed",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.USER_SPECIFIC,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default=False,
|
||||
description="User's sidebar collapsed state preference",
|
||||
category="user_preferences",
|
||||
admin_only=False
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="user_view_mode",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.USER_SPECIFIC,
|
||||
storage=SettingStorage.DATABASE,
|
||||
default="admin",
|
||||
description="User's current view mode (admin/user)",
|
||||
category="user_preferences",
|
||||
admin_only=False,
|
||||
choices=["admin", "user"]
|
||||
))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SYSTEM/ENVIRONMENT SETTINGS (Infrastructure, .env file)
|
||||
# These are NOT stored in the database
|
||||
# =============================================================================
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="SECRET_KEY",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.SYSTEM,
|
||||
storage=SettingStorage.ENV,
|
||||
default=None, # Required, no default
|
||||
description="Secret key for JWT token signing",
|
||||
category="security",
|
||||
sync_to_frontend=False
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="DATABASE_URL",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.SYSTEM,
|
||||
storage=SettingStorage.ENV,
|
||||
default="sqlite:////config/config.db",
|
||||
description="Database connection URL",
|
||||
category="infrastructure",
|
||||
sync_to_frontend=False
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="ALGORITHM",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.SYSTEM,
|
||||
storage=SettingStorage.ENV,
|
||||
default="HS256",
|
||||
description="JWT signing algorithm",
|
||||
category="security",
|
||||
sync_to_frontend=False
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="ACCESS_TOKEN_EXPIRE_MINUTES",
|
||||
type=SettingType.INTEGER,
|
||||
scope=SettingScope.SYSTEM,
|
||||
storage=SettingStorage.ENV,
|
||||
default=1440,
|
||||
description="JWT token expiration time in minutes",
|
||||
category="security",
|
||||
sync_to_frontend=False
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="ALLOWED_HOSTS",
|
||||
type=SettingType.LIST,
|
||||
scope=SettingScope.SYSTEM,
|
||||
storage=SettingStorage.ENV,
|
||||
default=[],
|
||||
description="Additional allowed CORS origins",
|
||||
category="security",
|
||||
sync_to_frontend=False
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="LOG_LEVEL",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.SYSTEM,
|
||||
storage=SettingStorage.ENV,
|
||||
default="info",
|
||||
description="Application logging level",
|
||||
category="infrastructure",
|
||||
choices=["debug", "info", "warning", "error", "critical"],
|
||||
sync_to_frontend=False
|
||||
))
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="DEBUG",
|
||||
type=SettingType.BOOLEAN,
|
||||
scope=SettingScope.SYSTEM,
|
||||
storage=SettingStorage.ENV,
|
||||
default=False,
|
||||
description="Enable debug mode",
|
||||
category="infrastructure",
|
||||
sync_to_frontend=False
|
||||
))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FRONTEND-ONLY SETTINGS (localStorage, not in database)
|
||||
# These are managed entirely by the frontend
|
||||
# =============================================================================
|
||||
|
||||
register_setting(SettingDefinition(
|
||||
key="token",
|
||||
type=SettingType.STRING,
|
||||
scope=SettingScope.USER_SPECIFIC,
|
||||
storage=SettingStorage.LOCAL_STORAGE,
|
||||
default=None,
|
||||
description="JWT authentication token",
|
||||
category="auth",
|
||||
sync_to_frontend=False # Managed by frontend only
|
||||
))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
def get_settings_by_category(category: str) -> list[SettingDefinition]:
|
||||
"""Get all settings in a specific category."""
|
||||
return [s for s in SETTINGS_REGISTRY.values() if s.category == category]
|
||||
|
||||
|
||||
def get_settings_by_scope(scope: SettingScope) -> list[SettingDefinition]:
|
||||
"""Get all settings with a specific scope."""
|
||||
return [s for s in SETTINGS_REGISTRY.values() if s.scope == scope]
|
||||
|
||||
|
||||
def get_settings_by_storage(storage: SettingStorage) -> list[SettingDefinition]:
|
||||
"""Get all settings stored in a specific location."""
|
||||
return [s for s in SETTINGS_REGISTRY.values() if s.storage == storage]
|
||||
|
||||
|
||||
def get_database_settings() -> list[SettingDefinition]:
|
||||
"""Get all settings that should be stored in the database."""
|
||||
return get_settings_by_storage(SettingStorage.DATABASE)
|
||||
|
||||
|
||||
def get_global_settings() -> list[SettingDefinition]:
|
||||
"""Get all global (non user-specific) settings."""
|
||||
return get_settings_by_scope(SettingScope.GLOBAL)
|
||||
|
||||
|
||||
def get_user_specific_settings() -> list[SettingDefinition]:
|
||||
"""Get all user-specific settings."""
|
||||
return get_settings_by_scope(SettingScope.USER_SPECIFIC)
|
||||
|
||||
|
||||
def get_default_value(key: str) -> Any:
|
||||
"""Get the default value for a setting."""
|
||||
if key in SETTINGS_REGISTRY:
|
||||
return SETTINGS_REGISTRY[key].default
|
||||
return None
|
||||
|
||||
|
||||
def get_all_defaults() -> dict[str, Any]:
|
||||
"""Get all default values as a dictionary."""
|
||||
return {key: setting.default for key, setting in SETTINGS_REGISTRY.items()}
|
||||
|
||||
|
||||
def get_database_defaults() -> dict[str, Any]:
|
||||
"""Get default values for all database-stored settings."""
|
||||
return {
|
||||
setting.key: setting.default
|
||||
for setting in get_database_settings()
|
||||
}
|
||||
|
||||
|
||||
def validate_setting_value(key: str, value: Any) -> bool:
|
||||
"""Validate a setting value against its definition."""
|
||||
if key not in SETTINGS_REGISTRY:
|
||||
return False
|
||||
|
||||
setting = SETTINGS_REGISTRY[key]
|
||||
|
||||
# Check type
|
||||
if setting.type == SettingType.BOOLEAN:
|
||||
if not isinstance(value, bool) and value not in ['true', 'false', 'True', 'False']:
|
||||
return False
|
||||
elif setting.type == SettingType.INTEGER:
|
||||
if not isinstance(value, int):
|
||||
try:
|
||||
int(value)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
elif setting.type == SettingType.STRING:
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
|
||||
# Check choices if defined
|
||||
if setting.choices and value not in setting.choices:
|
||||
# For booleans converted to strings
|
||||
if setting.type == SettingType.BOOLEAN:
|
||||
return True
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CATEGORY KEYS (for API endpoints)
|
||||
# =============================================================================
|
||||
|
||||
THEME_KEYS = [s.key for s in get_settings_by_category("theme")]
|
||||
MODULE_KEYS = [s.key for s in get_settings_by_category("modules")]
|
||||
AUTH_KEYS = [s.key for s in get_settings_by_category("auth")]
|
||||
UI_KEYS = [s.key for s in get_settings_by_category("ui")]
|
||||
USER_PREFERENCE_KEYS = [s.key for s in get_settings_by_category("user_preferences")]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PRINT SUMMARY (for debugging)
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n=== SETTINGS REGISTRY SUMMARY ===\n")
|
||||
|
||||
print(f"Total settings: {len(SETTINGS_REGISTRY)}")
|
||||
print(f" - Database settings: {len(get_database_settings())}")
|
||||
print(f" - Global settings: {len(get_global_settings())}")
|
||||
print(f" - User-specific settings: {len(get_user_specific_settings())}")
|
||||
|
||||
print("\n--- By Category ---")
|
||||
categories = set(s.category for s in SETTINGS_REGISTRY.values())
|
||||
for cat in sorted(categories):
|
||||
settings = get_settings_by_category(cat)
|
||||
print(f" {cat}: {len(settings)} settings")
|
||||
|
||||
print("\n--- Database Settings (stored in /config/config.db) ---")
|
||||
for setting in get_database_settings():
|
||||
scope_label = "GLOBAL" if setting.scope == SettingScope.GLOBAL else "USER"
|
||||
print(f" [{scope_label}] {setting.key}: {setting.type.value} = {setting.default}")
|
||||
Reference in New Issue
Block a user