""" 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" )) register_setting(SettingDefinition( key="theme_tab_bar_position", type=SettingType.STRING, scope=SettingScope.GLOBAL, storage=SettingStorage.DATABASE, default="top", description="Position of the tab bar (top, bottom, or responsive)", category="theme", choices=["top", "bottom", "responsive"] )) # ============================================================================= # 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" )) register_setting(SettingDefinition( key="modules_order", type=SettingType.LIST, scope=SettingScope.GLOBAL, storage=SettingStorage.DATABASE, default=["feature1", "feature2", "feature3"], description="Order of feature modules in sidebar", 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" )) # Password policy settings register_setting(SettingDefinition( key="password_min_length", type=SettingType.INTEGER, scope=SettingScope.GLOBAL, storage=SettingStorage.DATABASE, default=8, description="Minimum password length", category="security" )) register_setting(SettingDefinition( key="password_require_uppercase", type=SettingType.BOOLEAN, scope=SettingScope.GLOBAL, storage=SettingStorage.DATABASE, default=True, description="Require uppercase letters in passwords", category="security" )) register_setting(SettingDefinition( key="password_require_lowercase", type=SettingType.BOOLEAN, scope=SettingScope.GLOBAL, storage=SettingStorage.DATABASE, default=True, description="Require lowercase letters in passwords", category="security" )) register_setting(SettingDefinition( key="password_require_digit", type=SettingType.BOOLEAN, scope=SettingScope.GLOBAL, storage=SettingStorage.DATABASE, default=True, description="Require digits in passwords", category="security" )) register_setting(SettingDefinition( key="password_require_special", type=SettingType.BOOLEAN, scope=SettingScope.GLOBAL, storage=SettingStorage.DATABASE, default=False, description="Require special characters in passwords", category="security" )) # ============================================================================= # 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 (Frontend localStorage) # These settings are managed by the frontend per-browser/user-session. # If you need cross-device persistence, implement a per-user DB table + API. # ============================================================================= register_setting(SettingDefinition( key="user_theme_mode", type=SettingType.STRING, scope=SettingScope.USER_SPECIFIC, storage=SettingStorage.LOCAL_STORAGE, 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.LOCAL_STORAGE, 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.LOCAL_STORAGE, 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.LOCAL_STORAGE, 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}")