1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
"""
Instance-wide settings stored in the `hub_settings` table.
One reader per concern, so callers never touch raw strings or key names. A
missing row means the built-in default — an upgrade never changes behaviour on
its own, and a downgrade that drops the table just returns to defaults.
"""
from sqlalchemy.ext.asyncio import AsyncSession
from meshbay_hub.db.models import HubSetting
# Whether a member may create a group that is listed in the public directory and
# open for anyone to join. Off makes the hub private-groups-only.
ALLOW_PUBLIC_GROUPS = "allow_public_groups"
_DEFAULTS: dict[str, str] = {
ALLOW_PUBLIC_GROUPS: "true",
}
# ── 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
async def set_raw(db: AsyncSession, key: str, value: str) -> None:
"""Upsert. The caller owns the commit."""
row = await db.get(HubSetting, key)
if row:
row.value = value
else:
db.add(HubSetting(key=key, value=value))
async def get_bool(db: AsyncSession, key: str) -> bool:
raw = await get_raw(db, key)
if raw is None:
raw = _DEFAULTS.get(key, "false")
return raw == "true"
async def public_groups_allowed(db: AsyncSession) -> bool:
return await get_bool(db, ALLOW_PUBLIC_GROUPS)
|