# app/services/platform_settings_service.py """ Platform Settings Service Provides access to platform-wide settings with a resolution chain: 1. AdminSetting from database (can be set via admin UI) 2. Environment variables (from .env/config) 3. Hardcoded defaults This allows admins to override defaults without code changes, while still supporting environment-based configuration. """ import logging from typing import Any from sqlalchemy.orm import Session from app.core.config import settings from models.database.admin import AdminSetting logger = logging.getLogger(__name__) class PlatformSettingsService: """ Service for accessing platform-wide settings. Resolution order: 1. AdminSetting in database (highest priority) 2. Environment variable via config 3. Hardcoded default (lowest priority) """ # Mapping of setting keys to their config attribute names and defaults SETTINGS_MAP = { "default_storefront_locale": { "config_attr": "default_storefront_locale", "default": "fr-LU", "description": "Default locale for currency/number formatting (e.g., fr-LU, de-DE)", "category": "storefront", }, "default_currency": { "config_attr": "default_currency", "default": "EUR", "description": "Default currency code for the platform", "category": "storefront", }, } def get(self, db: Session, key: str) -> str | None: """ Get a setting value with full resolution chain. Args: db: Database session key: Setting key (e.g., 'default_storefront_locale') Returns: Setting value or None if not found """ # 1. Check AdminSetting in database admin_setting = db.query(AdminSetting).filter_by(key=key).first() if admin_setting and admin_setting.value: logger.debug(f"Setting '{key}' resolved from AdminSetting: {admin_setting.value}") return admin_setting.value # 2. Check environment/config setting_info = self.SETTINGS_MAP.get(key) if setting_info: config_attr = setting_info.get("config_attr") if config_attr and hasattr(settings, config_attr): value = getattr(settings, config_attr) logger.debug(f"Setting '{key}' resolved from config: {value}") return value # 3. Return hardcoded default default = setting_info.get("default") logger.debug(f"Setting '{key}' resolved from default: {default}") return default logger.warning(f"Unknown setting key: {key}") return None def get_storefront_locale(self, db: Session) -> str: """Get the default storefront locale.""" return self.get(db, "default_storefront_locale") or "fr-LU" def get_currency(self, db: Session) -> str: """Get the default currency.""" return self.get(db, "default_currency") or "EUR" def get_storefront_config(self, db: Session) -> dict[str, str]: """ Get all storefront-related settings as a dict. Returns: Dict with 'locale' and 'currency' keys """ return { "locale": self.get_storefront_locale(db), "currency": self.get_currency(db), } def set(self, db: Session, key: str, value: str, user_id: int | None = None) -> AdminSetting: """ Set a platform setting in the database. Args: db: Database session key: Setting key value: Setting value user_id: ID of user making the change (for audit) Returns: The created/updated AdminSetting """ setting_info = self.SETTINGS_MAP.get(key, {}) admin_setting = db.query(AdminSetting).filter_by(key=key).first() if admin_setting: admin_setting.value = value if user_id: admin_setting.last_modified_by_user_id = user_id else: admin_setting = AdminSetting( key=key, value=value, value_type="string", category=setting_info.get("category", "system"), description=setting_info.get("description", ""), last_modified_by_user_id=user_id, ) db.add(admin_setting) db.commit() # noqa: SVC-006 - Setting change is atomic, commit is intentional db.refresh(admin_setting) logger.info(f"Platform setting '{key}' set to '{value}' by user {user_id}") return admin_setting def get_all_storefront_settings(self, db: Session) -> dict[str, Any]: """ Get all storefront settings with their current values and metadata. Useful for admin UI to display current settings. Returns: Dict with setting info including current value and source """ result = {} for key, info in self.SETTINGS_MAP.items(): if info.get("category") == "storefront": current_value = self.get(db, key) # Determine source admin_setting = db.query(AdminSetting).filter_by(key=key).first() if admin_setting and admin_setting.value: source = "database" elif hasattr(settings, info.get("config_attr", "")): source = "environment" else: source = "default" result[key] = { "value": current_value, "source": source, "description": info.get("description", ""), "default": info.get("default"), } return result # Singleton instance platform_settings_service = PlatformSettingsService()