Skip to content

Expose single-use invite lifecycle on the generated code - #318

Merged
martsokha merged 1 commit into
mainfrom
feat/invite-code-lifecycle
Sep 20, 2026
Merged

martsokha merged 1 commit into
mainfrom
feat/invite-code-lifecycle

Conversation

@martsokha

@martsokha martsokha commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

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 WorkspaceInviteCode was a lossy, second-class projection of the invite — { inviteCode, workspaceId, workspaceHandle, role, expiresAt } — that renamed invitedRole → role and, crucially, dropped both the inviteId and the inviteStatus. So a client that generated a code couldn't correlate it back to its listInvites row (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_invite only transitions a still-pending row to accepted. 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 WorkspaceWebhookCreated already uses for "created resource + one-time secret":

pub struct WorkspaceInviteCode {
    pub invite_code: String,          // show-once secret, returned ONLY here
    #[serde(flatten)]
    pub invite: WorkspaceInvite,      // the full summary the listing returns
}

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 respondedAt on the WorkspaceInvite summary itself, so both the list and the code show when a single-use code was consumed.

Notes

  • 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; a separate used variant would split one concept in two (as discussed on the issue).
  • Show-once preserved. The raw inviteCode is still returned only from generate, never from the list.
  • Studio can now mark a spent code by correlating via inviteId and reading inviteStatus == 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 doc clean; nvisy-server lib suite passing.

🤖 Generated with Claude Code

https://claude.ai/code/session_018bKk1YEG4tZ69jzYVQvQL8

@martsokha martsokha added feat request for or implementation of a new feature server API handlers, middleware, auth labels Sep 20, 2026
@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Essentials

Run ID: dd58624b-4e13-49ac-9adc-5086b36fede3

📥 Commits

Reviewing files that changed from the base of the PR and between 9725004 and 203d9b6.

📒 Files selected for processing (2)
  • crates/nvisy-server/src/handler/response/workspace_invites.rs
  • crates/nvisy-server/src/handler/workspace_invites.rs

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.


📝 Walkthrough

Walkthrough

The 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.

Changes

Invite response state

Layer / File(s) Summary
Invite response contract and serialization
crates/nvisy-server/src/handler/response/workspace_invites.rs
WorkspaceInvite now includes optional respondedAt. WorkspaceInviteCode now flattens the complete invite beside inviteCode. Tests cover active and consumed invite serialization.
Invite-code generation integration
crates/nvisy-server/src/handler/workspace_invites.rs
generate_invite_code passes the invite by value. Its documentation describes the show-once code and invite status fields.

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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #317 requires usage state without exposing the raw secret in listings, correlation between generated codes and listed invites, and show-once handling. WorkspaceInviteCode now serializes `invit…
Out of Scope Changes check ✅ Passed The changes are limited to workspace invite response mapping, generated-code response construction and documentation, plus focused serialization tests. Each change supports issue #317 by exposing life…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: the generated single-use invite code response now exposes invite lifecycle information.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

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
martsokha force-pushed the feat/invite-code-lifecycle branch from 203d9b6 to 97157a4 Compare September 20, 2026 15:53
@martsokha
martsokha merged commit 362ca82 into main Sep 20, 2026
7 checks passed
@martsokha
martsokha deleted the feat/invite-code-lifecycle branch September 20, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat request for or implementation of a new feature server API handlers, middleware, auth

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invite API doesn't expose single-use state, so the UI can't reflect a consumed code

1 participant