refactor(backend): quota.measured helper, consistent admin gating, messageable predicate

- quota.measured() context manager folds the before/after units diff that the
  manual sync routes each re-spelled (also makes quota_remaining_today consistent).
- sync pause/resume now use Depends(admin_user) instead of inline role checks.
- is_messageable_user() unifies the 'real, active, non-suspended human' rule that
  was encoded three ways (WS auth, send recipient, and the SQL _messageable()).
This commit is contained in:
npeter83 2026-06-26 03:15:53 +02:00
parent 63701f5344
commit 5b81e22677
3 changed files with 38 additions and 27 deletions

View file

@ -103,6 +103,7 @@ def _serialize_msg(m: Message) -> dict:
def _messageable() -> list:
"""SQL clauses for "a real, active, non-suspended human" — for directory/recipient queries."""
return [
User.is_demo.is_(False),
User.is_active.is_(True),
@ -110,6 +111,11 @@ def _messageable() -> list:
]
def is_messageable_user(u: User | None) -> bool:
"""Python mirror of `_messageable()` for an already-loaded row (WS auth, send recipient)."""
return bool(u and not u.is_demo and u.is_active and not u.is_suspended)
def ensure_welcome(db: Session, user: User) -> None:
"""Create the one-time system welcome message for a (human) user the first time it's needed.
Idempotent: guarded on whether any system message already exists for them."""
@ -315,7 +321,7 @@ def send_message(
if payload.recipient_id == user.id:
raise HTTPException(status_code=400, detail="You can't message yourself.")
recipient = db.get(User, payload.recipient_id)
if recipient is None or recipient.is_demo or not recipient.is_active or recipient.is_suspended:
if not is_messageable_user(recipient):
raise HTTPException(status_code=404, detail="Recipient not available.")
if db.get(MessageKey, recipient.id) is None:
raise HTTPException(status_code=404, detail="That member hasn't set up messaging yet.")
@ -363,8 +369,7 @@ async def messages_ws(ws: WebSocket) -> None:
return
db = SessionLocal()
try:
u = db.get(User, uid)
ok = bool(u and not u.is_demo and not u.is_suspended and u.is_active)
ok = is_messageable_user(db.get(User, uid))
finally:
db.close()
if not ok: