from __future__ import annotations from backend.app.api.errors import AppError from backend.app.api.schemas import SettingsResponse, SettingsUpdateRequest from backend.app.db.settings_repository import SettingsRepository from backend.app.security.path_guard import PathGuard VALID_THEMES = {"default"} VALID_COLOR_MODES = {"dark", "light"} class SettingsService: def __init__(self, repository: SettingsRepository, path_guard: PathGuard): self._repository = repository self._path_guard = path_guard def get_settings(self) -> SettingsResponse: values = self._repository.get_settings() preferred_left = self._as_optional_str(values.get("preferred_startup_path_left")) preferred_right = self._as_optional_str(values.get("preferred_startup_path_right")) legacy_preferred = self._as_optional_str(values.get("preferred_startup_path")) selected_theme = self._normalize_theme(values.get("selected_theme")) selected_color_mode = self._normalize_color_mode(values.get("selected_color_mode")) return SettingsResponse( show_thumbnails=self._as_bool(values.get("show_thumbnails"), default=False), preferred_startup_path_left=preferred_left or legacy_preferred, preferred_startup_path_right=preferred_right, selected_theme=selected_theme, selected_color_mode=selected_color_mode, ) def update_settings(self, request: SettingsUpdateRequest) -> SettingsResponse: if request.show_thumbnails is not None: self._repository.set_setting("show_thumbnails", "true" if request.show_thumbnails else "false") if request.preferred_startup_path_left is not None: self._set_directory_setting("preferred_startup_path_left", request.preferred_startup_path_left) if request.preferred_startup_path_right is not None: self._set_directory_setting("preferred_startup_path_right", request.preferred_startup_path_right) if request.selected_theme is not None: self._repository.set_setting("selected_theme", self._validate_theme(request.selected_theme)) if request.selected_color_mode is not None: self._repository.set_setting("selected_color_mode", self._validate_color_mode(request.selected_color_mode)) return self.get_settings() def _set_directory_setting(self, key: str, value: str) -> None: normalized = value.strip() if not normalized: self._repository.set_setting(key, "") return resolved = self._path_guard.resolve_directory_path(normalized) self._repository.set_setting(key, resolved.relative) @staticmethod def _as_bool(value: str | None, default: bool) -> bool: if value is None: return default return value.strip().lower() in {"1", "true", "yes", "on"} @staticmethod def _as_optional_str(value: str | None) -> str | None: if value is None: return None normalized = value.strip() return normalized or None @staticmethod def _normalize_theme(value: str | None) -> str: normalized = (value or "").strip() return normalized if normalized in VALID_THEMES else "default" @staticmethod def _normalize_color_mode(value: str | None) -> str: normalized = (value or "").strip().lower() return normalized if normalized in VALID_COLOR_MODES else "dark" @staticmethod def _validate_theme(value: str) -> str: normalized = value.strip() if normalized not in VALID_THEMES: raise AppError( status_code=400, code="invalid_request", message="Theme must be one of: default", ) return normalized @staticmethod def _validate_color_mode(value: str) -> str: normalized = value.strip().lower() if normalized not in VALID_COLOR_MODES: raise AppError( status_code=400, code="invalid_request", message="Color mode must be one of: dark, light", ) return normalized