# retoor from .core import TTLCache, bump_cache_version, datetime, db, get_table, sync_local_cache, timezone from .soft_delete import soft_delete CUSTOMIZATION_GLOBAL_SCOPE = "global" CUSTOMIZATION_LANGS = ("css", "js") _customizations_cache = TTLCache(ttl=300, max_size=100) def _customization_key(owner_kind: str, owner_id: str, page_type: str) -> str: return f"{owner_kind}\x1f{owner_id}\x1f{page_type}" CUSTOMIZATION_PREF_COLUMNS = { "global": "cust_disable_global", "pagetype": "cust_disable_pagetype", } def get_customization_prefs(owner_kind: str, owner_id: str) -> dict: if owner_kind != "user" or "users" not in db.tables: return {"disable_global": False, "disable_pagetype": False} user = db["users"].find_one(uid=owner_id) if user is None: return {"disable_global": False, "disable_pagetype": False} return { "disable_global": bool(user.get("cust_disable_global", 0)), "disable_pagetype": bool(user.get("cust_disable_pagetype", 0)), } def set_customization_pref(owner_id: str, category: str, disabled: bool) -> None: column = CUSTOMIZATION_PREF_COLUMNS.get(category) if column is None: raise ValueError(f"Unknown customization category: {category}") from devplacepy.utils import clear_user_cache get_table("users").update( {"uid": owner_id, column: 1 if disabled else 0}, ["uid"] ) clear_user_cache(owner_id) bump_cache_version("customizations") def get_custom_overrides(owner_kind: str, owner_id: str, page_type: str) -> dict: sync_local_cache("customizations", _customizations_cache) key = _customization_key(owner_kind, owner_id, page_type) cached = _customizations_cache.get(key) if cached is not None: return cached result = {"css": "", "js": ""} if "user_customizations" in db.tables: prefs = get_customization_prefs(owner_kind, owner_id) scopes = (CUSTOMIZATION_GLOBAL_SCOPE, page_type) rows = db["user_customizations"].find( owner_kind=owner_kind, owner_id=owner_id, enabled=1, deleted_at=None, ) pieces: dict[str, dict[str, str]] = {lang: {} for lang in CUSTOMIZATION_LANGS} for row in rows: lang = row.get("lang") scope = row.get("scope") if lang not in pieces or scope not in scopes: continue if scope == CUSTOMIZATION_GLOBAL_SCOPE and prefs["disable_global"]: continue if scope != CUSTOMIZATION_GLOBAL_SCOPE and prefs["disable_pagetype"]: continue pieces[lang][scope] = row.get("code") or "" for lang in CUSTOMIZATION_LANGS: ordered = [pieces[lang][scope] for scope in scopes if scope in pieces[lang]] result[lang] = "\n".join(part for part in ordered if part.strip()) _customizations_cache.set(key, result) return result def get_custom_override( owner_kind: str, owner_id: str, scope: str, lang: str ) -> dict | None: if "user_customizations" not in db.tables: return None return db["user_customizations"].find_one( owner_kind=owner_kind, owner_id=owner_id, scope=scope, lang=lang, deleted_at=None, ) def list_custom_overrides(owner_kind: str, owner_id: str) -> list: if "user_customizations" not in db.tables: return [] return list( db["user_customizations"].find( owner_kind=owner_kind, owner_id=owner_id, deleted_at=None ) ) def set_custom_override( owner_kind: str, owner_id: str, scope: str, lang: str, code: str ) -> dict: from devplacepy.utils import generate_uid table = get_table("user_customizations") now = datetime.now(timezone.utc).isoformat() existing = table.find_one( owner_kind=owner_kind, owner_id=owner_id, scope=scope, lang=lang ) if existing: record = { "id": existing["id"], "code": code, "enabled": 1, "updated_at": now, "deleted_at": None, "deleted_by": None, } table.update(record, ["id"]) result = {**existing, **record} else: result = { "uid": generate_uid(), "owner_kind": owner_kind, "owner_id": owner_id, "scope": scope, "lang": lang, "code": code, "enabled": 1, "created_at": now, "updated_at": now, "deleted_at": None, "deleted_by": None, } table.insert(result) bump_cache_version("customizations") return result def delete_custom_override( owner_kind: str, owner_id: str, scope: str | None = None, lang: str | None = None, deleted_by: str | None = None, ) -> int: if "user_customizations" not in db.tables: return 0 criteria: dict = {"owner_kind": owner_kind, "owner_id": owner_id} if scope is not None: criteria["scope"] = scope if lang is not None: criteria["lang"] = lang count = soft_delete( "user_customizations", deleted_by or f"{owner_kind}:{owner_id}", **criteria ) bump_cache_version("customizations") return int(count)