Expose single-use invite lifecycle on the generated code - #318
Merged
Merged
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Essentials Run ID: 📒 Files selected for processing (2)
Included review availability: 1 review is currently available. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour. 📝 WalkthroughWalkthroughThe invite API now exposes optional consumption timestamps and status in invite-code responses. The raw code remains show-once, while the response also includes flattened invite identifiers and metadata. ChangesInvite response state
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~15 minutes Change: Feature · Severity of issue fixed: Medium 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
Open invite codes are single-use, but the generate response
(`WorkspaceInviteCode`) was a lossy, second-class projection of the
invite — `{ inviteCode, workspaceId, workspaceHandle, role, expiresAt }`,
renaming `invitedRole` to `role` and dropping the id and status. A client
that generated a code had no way to correlate it to its listing row or
tell an active code from a consumed one.
Rework the generate response to compose the full invite summary plus the
show-once secret, mirroring `WorkspaceWebhookCreated`:
WorkspaceInviteCode { inviteCode, #[serde(flatten)] invite: WorkspaceInvite }
So a generated code now carries `inviteId` (correlate to the list),
`inviteStatus` (pending = active, accepted = consumed), `respondedAt`
(when it was used), and the rest — one canonical invite shape instead of
two divergent ones. The raw `inviteCode` stays show-once: it is still
returned only here, never from the list.
Also surface `respondedAt` on the `WorkspaceInvite` summary itself, so
both the list and the code show when a single-use code was consumed. No
schema/DB change: consumption was already recorded as
`invite_status = accepted` + `responded_at`; this only makes it
observable. No new `InviteStatus` variant — `accepted` already means
consumed for open and targeted invites alike.
martsokha
force-pushed
the
feat/invite-code-lifecycle
branch
from
September 20, 2026 15:53
203d9b6 to
97157a4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #317.
Problem
Open invite codes are single-use, but the API gave a client no way to tell an active code from a consumed one. The generate response
WorkspaceInviteCodewas a lossy, second-class projection of the invite —{ inviteCode, workspaceId, workspaceHandle, role, expiresAt }— that renamedinvitedRole→roleand, crucially, dropped both theinviteIdand theinviteStatus. So a client that generated a code couldn't correlate it back to itslistInvitesrow (the raw token is show-once, absent from the list) or read its lifecycle.(The listing already carried
inviteStatus, and single-use is already enforced server-side —accept_workspace_inviteonly transitions a still-pendingrow toaccepted. The gap was purely the generate response.)The clean fix
Rather than bolt a couple of fields onto the divergent projection, make the generate response compose the full invite plus the show-once secret — the exact pattern
WorkspaceWebhookCreatedalready uses for "created resource + one-time secret":A generated code now carries
inviteId(correlate to the list row),inviteStatus(pending= active,accepted= consumed),respondedAt(when it was used),invitedRole, and the rest — one canonical invite shape instead of two.Also surfaces
respondedAton theWorkspaceInvitesummary itself, so both the list and the code show when a single-use code was consumed.Notes
invite_status = accepted+responded_at; this only makes it observable.InviteStatusvariant.acceptedalready means consumed for open and targeted invites alike; a separateusedvariant would split one concept in two (as discussed on the issue).inviteCodeis still returned only from generate, never from the list.inviteIdand readinginviteStatus == accepted, without ever re-fetching the secret — so the "copy the link again" anti-pattern stays declined.Verification
cargo check,clippy --all-targets --all-features -D warnings,+nightly fmt --check,RUSTDOCFLAGS=-D warnings cargo docclean;nvisy-serverlib suite passing.🤖 Generated with Claude Code
https://claude.ai/code/session_018bKk1YEG4tZ69jzYVQvQL8