rswag-online/backend/app/services/space_service.py

105 lines
2.9 KiB
Python

"""Space (tenant) service for multi-subdomain support."""
from pathlib import Path
import yaml
from pydantic import BaseModel
from app.config import get_settings
settings = get_settings()
class SpaceTheme(BaseModel):
"""Theme configuration for a space."""
primary: str = "195 80% 45%"
primary_foreground: str = "0 0% 100%"
secondary: str = "45 80% 55%"
secondary_foreground: str = "222.2 47.4% 11.2%"
background: str = "0 0% 100%"
foreground: str = "222.2 84% 4.9%"
card: str = "0 0% 100%"
card_foreground: str = "222.2 84% 4.9%"
popover: str = "0 0% 100%"
popover_foreground: str = "222.2 84% 4.9%"
muted: str = "210 40% 96.1%"
muted_foreground: str = "215.4 16.3% 46.9%"
accent: str = "210 40% 96.1%"
accent_foreground: str = "222.2 47.4% 11.2%"
destructive: str = "0 84.2% 60.2%"
destructive_foreground: str = "210 40% 98%"
border: str = "214.3 31.8% 91.4%"
input: str = "214.3 31.8% 91.4%"
ring: str = "195 80% 45%"
class Space(BaseModel):
"""Space configuration."""
id: str
name: str
tagline: str = ""
description: str = ""
domain: str = ""
footer_text: str = ""
theme: SpaceTheme = SpaceTheme()
design_filter: str = "all"
logo_url: str | None = None
design_tips: list[str] = []
class SpaceService:
"""Service for loading and resolving spaces."""
def __init__(self):
self.spaces_path = Path(settings.spaces_path)
self._cache: dict[str, Space] = {}
self._loaded = False
def _ensure_loaded(self):
if self._loaded:
return
self._load_all()
self._loaded = True
def _load_all(self):
if not self.spaces_path.exists():
return
for space_dir in self.spaces_path.iterdir():
if not space_dir.is_dir():
continue
config_path = space_dir / "space.yaml"
if not config_path.exists():
continue
try:
with open(config_path) as f:
data = yaml.safe_load(f)
space = Space(**data)
self._cache[space.id] = space
except Exception:
continue
def get_space(self, space_id: str) -> Space | None:
"""Get a space by its ID."""
self._ensure_loaded()
return self._cache.get(space_id)
def get_default(self) -> Space:
"""Get the default space."""
self._ensure_loaded()
return self._cache.get(
"default",
Space(id="default", name="rSwag", domain="rswag.online"),
)
def list_spaces(self) -> list[Space]:
"""List all spaces."""
self._ensure_loaded()
return list(self._cache.values())
def clear_cache(self):
"""Clear the cache to force reload."""
self._cache.clear()
self._loaded = False