diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-23 19:30:47 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-23 19:30:47 +0200 |
| commit | 95cec0e0bbc28e930f297f44bbd3dbf4d63f0bc2 (patch) | |
| tree | 355ac2b769885b368d45846fe4c3c59cc8b3865b /packages/meshbay-hub/src/meshbay_hub/api/invite_links.py | |
| parent | d87f05f9f131aa7cc92f53355c5fe63be01aa516 (diff) | |
| download | meshbay-95cec0e0bbc28e930f297f44bbd3dbf4d63f0bc2.tar.gz | |
fix(hub): a redeemed invitation link leaves the owner's list
The list under "Invite by link" answered every ticket the group had ever
minted, so a link that somebody had already used sat there saying "used by
<name>" for the thirty days of KEEP_REDEEMED — beside the member row it had
just produced, and above the links that still wait for somebody, which are
the only ones there is anything to do about. The node's own `member list`
had never shown them: it selects `used_at IS NULL`.
The listing now selects `redeemed_by IS NULL`, and drops the `redeemed`
status and the `redeemed_by` field with it. The row itself still lives for
KEEP_REDEEMED, which is what lets a reload or a second tab of the invitation
page be answered rather than refused; its comment says that now instead of
naming a list it is no longer in.
The SPA filters too, because the desktop client's copy of this interface can
be newer than the hub it is signed into.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/invite_links.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/invite_links.py | 27 |
1 files changed, 17 insertions, 10 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/invite_links.py b/packages/meshbay-hub/src/meshbay_hub/api/invite_links.py index d44576b..f33dc6e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/invite_links.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/invite_links.py @@ -43,7 +43,9 @@ MAX_OUTSTANDING_PER_GROUP = 20 # A node's invitation lifetime is the operator's setting (7 days by default); # the ticket follows it, up to this. MAX_LIFETIME = timedelta(days=30) -# How long a redeemed link stays in the owner's list, saying who used it. +# How long a spent link is kept before it is forgotten. The owner is not shown +# it — the person is in the group — but while the row is here, a reload or a +# second tab of the invitation page still answers the account that used it. KEEP_REDEEMED = timedelta(days=30) _TICKET = re.compile(r"^[A-Za-z0-9_-]{22}$") # secrets.token_urlsafe(16) @@ -205,14 +207,21 @@ async def list_invite_links( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): - """The owner's view: who each link was for, masked, and whether it was used.""" + """ + The owner's view: the links nobody has used yet, masked. + + A redeemed one is left out. The person it let in has a row of their own in + the members list, so keeping the link there too says the same thing twice + and pushes down the links that still wait for somebody — which are the ones + the owner can act on, by cancelling them. + """ await _owned_group(db, group_id, current_user) rows = (await db.execute( - select(GroupInviteLink, User.username) - .outerjoin(User, User.id == GroupInviteLink.redeemed_by) - .where(GroupInviteLink.group_id == group_id) + select(GroupInviteLink) + .where(GroupInviteLink.group_id == group_id, + GroupInviteLink.redeemed_by.is_(None)) .order_by(GroupInviteLink.created_at.desc()) - .limit(200))).all() + .limit(200))).scalars().all() now = datetime.now(UTC) return {"links": [{ "link_id": r.id, @@ -220,10 +229,8 @@ async def list_invite_links( "node_invite_id": r.node_invite_id, "created_at": _aware(r.created_at).isoformat(), "expires_at": _aware(r.expires_at).isoformat(), - "status": ("redeemed" if r.redeemed_by - else "expired" if _aware(r.expires_at) <= now else "pending"), - "redeemed_by": name, - } for r, name in rows]} + "status": "expired" if _aware(r.expires_at) <= now else "pending", + } for r in rows]} @router.delete("/{group_id}/invite-links/{link_id}") |