diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/hub_settings.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/hub_settings.py | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/hub_settings.py b/packages/meshbay-hub/src/meshbay_hub/hub_settings.py index 280d1e2..1c2d2c4 100644 --- a/packages/meshbay-hub/src/meshbay_hub/hub_settings.py +++ b/packages/meshbay-hub/src/meshbay_hub/hub_settings.py @@ -19,6 +19,72 @@ _DEFAULTS: dict[str, str] = { } +# ── Mail bounds ────────────────────────────────────────────────────────────── +# +# Stored here rather than read from `hub.toml` at each use, because an operator +# has to be able to change them while the hub is serving: the hour the budget +# runs out is exactly when nobody wants to edit a file and restart. The TOML +# values are the defaults these fall back to, so an instance that never touches +# the panel behaves as its configuration file says. + +MAIL_KEYS = ( + "destination_cooldown_seconds", + "destination_daily_cap", + "hourly_budget", + "hourly_reserved_for_recovery", + "verification_resend_cooldown", + "reset_cooldown", + "email_change_cooldown", +) + +# What a value may be. A cooldown of zero disables it, which is a legitimate +# thing for an operator to want; a budget of zero would stop the hub sending +# anything at all, which is not, so those start at one. The upper bounds are +# there because this is a number typed into a web form. +MAIL_BOUNDS: dict[str, tuple[int, int]] = { + "destination_cooldown_seconds": (0, 86_400), + "destination_daily_cap": (1, 1_000), + "hourly_budget": (1, 100_000), + "hourly_reserved_for_recovery": (0, 100_000), + "verification_resend_cooldown": (0, 86_400), + "reset_cooldown": (0, 86_400), + "email_change_cooldown": (0, 2_592_000), # 30 days +} + +_mail_defaults: dict[str, int] = {} + + +def set_mail_defaults(mail_cfg) -> None: + """Record what `hub.toml` said. Called once, at startup.""" + global _mail_defaults + _mail_defaults = {k: int(getattr(mail_cfg, k)) for k in MAIL_KEYS} + + +def mail_default(key: str) -> int: + return _mail_defaults.get(key, 0) + + +def clamp_mail_value(key: str, value: int) -> int: + low, high = MAIL_BOUNDS[key] + return max(low, min(high, int(value))) + + +async def get_int(db: AsyncSession, key: str, fallback: int) -> int: + raw = await get_raw(db, key) + if raw is None: + return fallback + try: + return int(raw) + except ValueError: + # A row that cannot be read is not a reason to send without a bound. + return fallback + + +async def mail_limits(db: AsyncSession) -> dict[str, int]: + """Every mail bound, stored value or configured default.""" + return {k: await get_int(db, f"mail.{k}", mail_default(k)) for k in MAIL_KEYS} + + async def get_raw(db: AsyncSession, key: str) -> str | None: row = await db.get(HubSetting, key) return row.value if row else None |