aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/api/admin.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/admin.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/admin.py59
1 files changed, 55 insertions, 4 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/admin.py b/packages/meshbay-hub/src/meshbay_hub/api/admin.py
index 219e8a9..087c221 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/admin.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/admin.py
@@ -38,12 +38,23 @@ class GroupPatchRequest(BaseModel):
class SettingsPatchRequest(BaseModel):
allow_public_groups: bool | None = None
+ # Every mail bound, each optional: the panel sends only what changed.
+ mail: dict[str, int] | None = None
# ── Instance settings ────────────────────────────────────────────────────────
-def _settings_payload(allow_public_groups: bool) -> dict:
- return {"allow_public_groups": allow_public_groups}
+def _settings_payload(allow_public_groups: bool, mail: dict) -> dict:
+ return {
+ "allow_public_groups": allow_public_groups,
+ "mail": mail,
+ # So the panel can show what a field falls back to, and label the
+ # bounds it will refuse — rather than the operator finding out by
+ # having a value silently clamped.
+ "mail_defaults": {k: hub_settings.mail_default(k)
+ for k in hub_settings.MAIL_KEYS},
+ "mail_bounds": {k: list(v) for k, v in hub_settings.MAIL_BOUNDS.items()},
+ }
@router.get("/settings")
@@ -52,7 +63,9 @@ async def admin_get_settings(
db: AsyncSession = Depends(get_db),
):
"""Instance-wide policy an admin controls from the panel. Moderators may read."""
- return _settings_payload(await hub_settings.public_groups_allowed(db))
+ return _settings_payload(
+ await hub_settings.public_groups_allowed(db),
+ await hub_settings.mail_limits(db))
@router.patch("/settings")
@@ -82,7 +95,45 @@ async def admin_patch_settings(
))
await db.commit()
- return _settings_payload(await hub_settings.public_groups_allowed(db))
+ if body.mail:
+ unknown = sorted(set(body.mail) - set(hub_settings.MAIL_KEYS))
+ if unknown:
+ raise HTTPException(
+ status_code=422, detail=f"Unknown mail setting(s): {unknown}")
+ changed = []
+ for key, value in body.mail.items():
+ clamped = hub_settings.clamp_mail_value(key, value)
+ await hub_settings.set_raw(db, f"mail.{key}", str(clamped))
+ changed.append(f"{key}={clamped}")
+ log.info("Mail bounds changed by %s: %s",
+ current_user.username, ", ".join(changed))
+ db.add(IPLog(
+ user_id=current_user.id,
+ event="admin_mail_limits_update",
+ ip_address="admin",
+ detail=", ".join(changed)[:255],
+ ))
+ await db.commit()
+
+ return _settings_payload(
+ await hub_settings.public_groups_allowed(db),
+ await hub_settings.mail_limits(db))
+
+
+@router.get("/mail")
+async def admin_mail_status(
+ current_user: User = Depends(require_moderator),
+ db: AsyncSession = Depends(get_db),
+):
+ """Is the hub still sending, and how much of the hour is left.
+
+ There was no way to see this at all: a refusal was a line in the journal,
+ so an instance that had stopped sending registration codes looked, from the
+ panel, exactly like one that had no sign-ups.
+ """
+ from meshbay_hub import mail
+
+ return await mail.status(db)
# ── Stats ────────────────────────────────────────────────────────────────────