aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/hub_settings.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/hub_settings.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/hub_settings.py66
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