from abc import ABC, abstractmethod import re from typing import Optional from selfprivacy_api.utils import ( ReadUserData, WriteUserData, check_if_subdomain_is_taken, ) class ServiceConfigItem(ABC): id: str description: str widget: str type: str @abstractmethod def get_value(self, service_id): pass @abstractmethod def set_value(self, value, service_id): pass @abstractmethod def validate_value(self, value): return True def as_dict(self, service_options): return { "id": self.id, "type": self.type, "description": self.description, "widget": self.widget, "value": self.get_value(service_options), } class StringServiceConfigItem(ServiceConfigItem): def __init__( self, id: str, default_value: str, description: str, regex: Optional[str] = None, widget: Optional[str] = None, allow_empty: bool = False, ): if widget == "subdomain" and not regex: raise ValueError("Subdomain widget requires regex") self.id = id self.type = "string" self.default_value = default_value self.description = description self.regex = re.compile(regex) if regex else None self.widget = widget if widget else "text" self.allow_empty = allow_empty def get_value(self, service_id): with ReadUserData() as user_data: if "modules" in user_data and service_id in user_data["modules"]: return user_data["modules"][service_id].get(self.id, self.default_value) return self.default_value def set_value(self, value, service_id): if not self.validate_value(value): raise ValueError(f"Value {value} is not valid") with WriteUserData() as user_data: if "modules" not in user_data: user_data["modules"] = {} if service_id not in user_data["modules"]: user_data["modules"][service_id] = {} user_data["modules"][service_id][self.id] = value def as_dict(self, service_options): return { "id": self.id, "type": self.type, "description": self.description, "widget": self.widget, "value": self.get_value(service_options), "default_value": self.default_value, "regex": self.regex.pattern if self.regex else None, } def validate_value(self, value): if not isinstance(value, str): return False if not self.allow_empty and not value: return False if self.regex and not self.regex.match(value): return False if self.widget == "subdomain": if check_if_subdomain_is_taken(value): return False return True class BoolServiceConfigItem(ServiceConfigItem): def __init__( self, id: str, default_value: bool, description: str, widget: Optional[str] = None, ): self.id = id self.type = "bool" self.default_value = default_value self.description = description self.widget = widget if widget else "switch" def get_value(self, service_id): with ReadUserData() as user_data: if "modules" in user_data and service_id in user_data["modules"]: return user_data["modules"][service_id].get(self.id, self.default_value) return self.default_value def set_value(self, value, service_id): if not self.validate_value(value): raise ValueError(f"Value {value} is not a boolean") with WriteUserData() as user_data: if "modules" not in user_data: user_data["modules"] = {} if service_id not in user_data["modules"]: user_data["modules"][service_id] = {} user_data["modules"][service_id][self.id] = value def as_dict(self, service_options): return { "id": self.id, "type": self.type, "description": self.description, "widget": self.widget, "value": self.get_value(service_options), "default_value": self.default_value, } def validate_value(self, value): return isinstance(value, bool) class EnumServiceConfigItem(ServiceConfigItem): def __init__( self, id: str, default_value: str, description: str, options: list[str], widget: Optional[str] = None, ): self.id = id self.type = "enum" self.default_value = default_value self.description = description self.options = options self.widget = widget if widget else "select" def get_value(self, service_id): with ReadUserData() as user_data: if "modules" in user_data and service_id in user_data["modules"]: return user_data["modules"][service_id].get(self.id, self.default_value) return self.default_value def set_value(self, value, service_id): if not self.validate_value(value): raise ValueError(f"Value {value} is not in options") with WriteUserData() as user_data: if "modules" not in user_data: user_data["modules"] = {} if service_id not in user_data["modules"]: user_data["modules"][service_id] = {} user_data["modules"][service_id][self.id] = value def as_dict(self, service_options): return { "id": self.id, "type": self.type, "description": self.description, "widget": self.widget, "value": self.get_value(service_options), "default_value": self.default_value, "options": self.options, } def validate_value(self, value): if not isinstance(value, str): return False return value in self.options # TODO: unused for now class IntServiceConfigItem(ServiceConfigItem): def __init__( self, id: str, default_value: int, description: str, widget: Optional[str] = None, min_value: Optional[int] = None, max_value: Optional[int] = None, ) -> None: self.id = id self.type = "int" self.default_value = default_value self.description = description self.widget = widget if widget else "number" self.min_value = min_value self.max_value = max_value def get_value(self, service_id): with ReadUserData() as user_data: if "modules" in user_data and service_id in user_data["modules"]: return user_data["modules"][service_id].get(self.id, self.default_value) return self.default_value def set_value(self, value, service_id): if not self.validate_value(value): raise ValueError(f"Value {value} is not valid") with WriteUserData() as user_data: if "modules" not in user_data: user_data["modules"] = {} if service_id not in user_data["modules"]: user_data["modules"][service_id] = {} user_data["modules"][service_id][self.id] = value def as_dict(self, service_options): return { "id": self.id, "type": self.type, "description": self.description, "widget": self.widget, "value": self.get_value(service_options), "default_value": self.default_value, "min_value": self.min_value, "max_value": self.max_value, } def validate_value(self, value): if not isinstance(value, int): return False return (self.min_value is None or value >= self.min_value) and ( self.max_value is None or value <= self.max_value )