refactor(api): unify namespace membership intake and carry a typed invite payload#6646
Open
otavio wants to merge 1 commit into
Open
refactor(api): unify namespace membership intake and carry a typed invite payload#6646otavio wants to merge 1 commit into
otavio wants to merge 1 commit into
Conversation
|
Claude finished @otavio's task in 2m 12s —— View job Code Review CompleteReviewed 11 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is. A few things I verified while reviewing, for the record:
To request another review round, comment |
Both "Add member" and "Generate invite link" now delegate to one unexported intakeMembership helper, so the invite rules live in a single place: the invited email is always lowercased before lookup/upsert and the whole write runs inside store.WithTransaction on both paths. This removes the earlier drift where the link path skipped lowercasing (producing duplicate placeholder accounts) and ran without a transaction. GenerateInvitationLink's inlined signature/expiry copy is deleted in favor of the shared create/resend helpers, and its tenant is now uuid-validated to match AddNamespaceMember. The misspelled FowardedHost request field is renamed to ForwardedHost; the bound X-Forwarded-Host header is unchanged. The "member invited" event now carries one typed MembershipInvitationNotification (signature, expiry, recipient email + name, forwarded proto + host) end to end: assembled once at intake, passed through the widened OnMembershipInvited hook and the internal client (JSON-encoded over the worker's []byte transport), replacing the hand-rolled positional colon-delimited string. The core<->cloud handoff is now a single shared type instead of twin prose comments. Fixes: #6645
d938a39 to
da0a63a
Compare
Member
Author
|
/review |
|
Claude finished @otavio's task in 3m 51s —— View job Code Review CompleteReviewed 11 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is. A few things verified during this round, for the record:
To request another review round, comment |
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.
What
Collapses the two namespace-membership-intake code paths — "Add member" and "Generate invite link" — onto one shared, unexported
intakeMembershiphelper, and replaces the hand-rolled positional worker payload with a single typed notification carried end to end.Why
The invite flow was written twice and had drifted in user-visible ways: the link path skipped lowercasing the invited email (producing two placeholder accounts for
Jane@x.comandjane@x.com), ran without a store transaction (half-created invitations on partial failure), and validated the tenant differently. Separately, the code firing the "member invited" hook held the fully-populated invitation but threw it away, forwarding onlytenant:user:proto:hostas a colon-delimited string — forcing the worker to re-read the database three times to rebuild what the caller already had, over a contract asserted only in twin prose comments across the shellhub↔cloud boundary.Closes #6645
Changes
AddNamespaceMemberandGenerateInvitationLinkbecome thin adapters overintakeMembership, which owns resolve-or-upsert, the duplicate-member check, the direct-membership short-circuit, and create-or-resend. The email is always lowercased and the whole write always runs insidestore.WithTransaction, so the two paths behave identically.GenerateInvitationLink's inlined signature/expiry copy is deleted in favor of the sharedcreateMembershipInvitation/resendMembershipInvitationhelpers (single source for the 7-day expiry and pairingcode signature).MembershipInvitationNotification— the typed, email-relevant snapshot (signature, expiry, recipient email + name, forwarded proto + host). Deliberately no role or namespace name; the template uses neither.OnMembershipInvitedandInviteMemberwiden to carry the notification; the internal client JSON-encodes it over the worker's existing[]bytetransport (task pattern unchanged).FowardedHostrenamed toForwardedHost(boundX-Forwarded-Hostheader unchanged, wire-compatible);GenerateInvitationLink.TenantIDnow uuid-validated to matchNamespaceAddMember.Testing
Exercised through the two public methods with mockery: new cases pin email-lowercasing on both paths,
WithTransactionusage, expired-invitation resend, the direct-membership short-circuit firing no hook, and a fully-populated post-commit notification. The cloud consumer side (worker + hook) moves in lockstep — see the paired PR on shellhub-io/cloud. Deploy both together: thecloud-api:invitespayload changes from a positional string to JSON, and the shallow invite queue drains rather than carrying a dual-format reader.