99 lines
2.9 KiB
Python
99 lines
2.9 KiB
Python
"""
|
|
Key/value store for admin-configurable site settings.
|
|
|
|
Values are strings on disk; the typed helpers below do the coercion. Reads
|
|
are cached per process and the cache is dropped on write, so the settings
|
|
page is authoritative without hitting the database on every request.
|
|
|
|
Created by: Jess Rogerson (yelling commands at Claude.AI)
|
|
"""
|
|
|
|
from datetime import datetime
|
|
import threading
|
|
|
|
from app import db
|
|
|
|
# Defaults are the behaviour LIM had before settings existed, except where
|
|
# noted — anything not yet stored falls back to these.
|
|
DEFAULTS = {
|
|
'allow_registration': 'true',
|
|
# Visibility given to newly created sets.
|
|
'default_visibility': 'private',
|
|
# Send logged-in users straight to their dashboard instead of the
|
|
# marketing homepage.
|
|
'home_redirect_dashboard': 'true',
|
|
}
|
|
|
|
VISIBILITIES = ('private', 'shared', 'public')
|
|
|
|
_cache = {}
|
|
_cache_lock = threading.Lock()
|
|
|
|
|
|
class SiteSetting(db.Model):
|
|
"""One configurable setting."""
|
|
|
|
__tablename__ = 'site_settings'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
key = db.Column(db.String(64), unique=True, nullable=False, index=True)
|
|
value = db.Column(db.Text, nullable=True)
|
|
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow,
|
|
onupdate=datetime.utcnow)
|
|
|
|
def __repr__(self):
|
|
return f'<SiteSetting {self.key}={self.value!r}>'
|
|
|
|
# -- reads --------------------------------------------------------------
|
|
|
|
@classmethod
|
|
def get(cls, key, default=None):
|
|
with _cache_lock:
|
|
if key in _cache:
|
|
return _cache[key]
|
|
try:
|
|
row = cls.query.filter_by(key=key).first()
|
|
except Exception:
|
|
# Table may not exist yet (first boot, before create_all).
|
|
return DEFAULTS.get(key, default)
|
|
value = row.value if row else DEFAULTS.get(key, default)
|
|
with _cache_lock:
|
|
_cache[key] = value
|
|
return value
|
|
|
|
@classmethod
|
|
def get_bool(cls, key, default=False):
|
|
value = cls.get(key)
|
|
if value is None:
|
|
return default
|
|
return str(value).strip().lower() in ('1', 'true', 'yes', 'on')
|
|
|
|
@classmethod
|
|
def all_settings(cls):
|
|
merged = dict(DEFAULTS)
|
|
try:
|
|
for row in cls.query.all():
|
|
merged[row.key] = row.value
|
|
except Exception:
|
|
pass
|
|
return merged
|
|
|
|
# -- writes -------------------------------------------------------------
|
|
|
|
@classmethod
|
|
def set(cls, key, value):
|
|
if isinstance(value, bool):
|
|
value = 'true' if value else 'false'
|
|
row = cls.query.filter_by(key=key).first()
|
|
if row:
|
|
row.value = value
|
|
else:
|
|
db.session.add(cls(key=key, value=value))
|
|
cls.clear_cache()
|
|
return value
|
|
|
|
@classmethod
|
|
def clear_cache(cls):
|
|
with _cache_lock:
|
|
_cache.clear()
|