Skip to content

Require invitation flow for existing users in competition contexts#373

Open
zacjones93 wants to merge 1 commit intomainfrom
claude/fix-team-invite-email-qAoPc
Open

Require invitation flow for existing users in competition contexts#373
zacjones93 wants to merge 1 commit intomainfrom
claude/fix-team-invite-email-qAoPc

Conversation

@zacjones93
Copy link
Copy Markdown
Contributor

@zacjones93 zacjones93 commented Apr 3, 2026

Summary

Modified the user invitation logic to ensure existing users invited to competition teams go through the invitation acceptance flow rather than being added directly. This allows them to complete required registration steps (questions, waivers) before joining the team.

Key Changes

  • Conditional logic for existing users: Split the flow based on context:

    • Competition context: Existing users now fall through to the invitation creation path instead of being added directly to the team
    • Non-competition context: Existing users are added directly to the team as before, with immediate session updates and return
  • Removed automatic team membership: Eliminated the direct insertion into teamMembershipTable for existing users in competition contexts, along with the associated logic for adding them to the competition_event team

  • Simplified return path: Non-competition invites now return immediately after adding the user, avoiding unnecessary invitation creation

Implementation Details

  • The change preserves backward compatibility for non-competition team invitations
  • Competition invites now consistently use the invitation acceptance flow, ensuring users complete all required registration steps before team membership is finalized
  • The 30-day invitation expiration window applies to both new users and existing users in competition contexts

https://claude.ai/code/session_0195edp7LRhJY25VXrFhNzMr


Summary by cubic

Require invitation acceptance for existing users when invited to competition teams, so they receive an email and complete registration (questions, waivers) before joining. Non-competition invites still add existing users immediately.

  • Bug Fixes
    • Existing users in competition context now get an invitation with a 30-day expiry; team membership happens on acceptance.
    • Removed silent auto-add and the automatic join to the competition_event team.
    • Non-competition invites keep direct add behavior and update sessions immediately.

Written for commit ceb2e23. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved team membership and invitation handling for more consistent user registration workflows

…t auto-add

When a teammate already had a WODsmith account, inviteUserToTeamInternal
silently added them to the team without sending an email or creating an
invitation. This meant they were never notified about their competition
registration and couldn't complete required registration actions (questions,
waivers).

Now, existing users in a competition context go through the same invitation
flow as new users: an invitation is created, the email is sent, and the
acceptance flow handles team membership and registration requirements.

https://claude.ai/code/session_0195edp7LRhJY25VXrFhNzMr
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

Walkthrough

The inviteUserToTeamInternal function in the registration module is refactored to conditionally handle team membership insertion based on whether a competition context exists. When present, membership insertion is skipped to enable invitation creation; when absent, membership is inserted directly with session updates and an early return.

Changes

Cohort / File(s) Summary
Team Membership Conditional Logic
apps/wodsmith-start/src/server/registration.ts
Restructured inviteUserToTeamInternal to conditionally insert team membership based on competition context presence; removed prior competition_event team handling and deduplicated session update calls.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through logic trees,
Conditional paths now bend with ease—
When contests call, invites take flight,
When silence reigns, memberships unite,
Duplicates gone, the code runs light! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: requiring an invitation flow (instead of direct addition) for existing users in competition contexts.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fix-team-invite-email-qAoPc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 3, 2026

🚀 Preview Deployed

URL: https://wodsmith-app-pr-373.zacjones93.workers.dev

Detail Value
Commit f38a426
Stage pr-373
Deployed 2026-04-03T01:26:40.571Z

This comment is automatically updated on each push to this PR.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/wodsmith-start/src/server/registration.ts (1)

211-233: Add regression coverage for the new branch split.

This is the behavior this PR is changing, so it needs explicit tests for both cases: existing user + competitionContext should create an invitation and not a membership, while existing user without competitionContext should create the membership and return early.

As per coding guidelines, Add exactly one // @lat: [[section-id]] comment (or # @lat: for Python) next to each relevant test to reference its test spec, not at the top of the file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/wodsmith-start/src/server/registration.ts` around lines 211 - 233, Add
two regression tests covering the new branch split in registration: (1) for an
existing user with competitionContext=true assert that an invitation is created
(no team_membership row), the function returns invitationSent=true (or
appropriate shape), and updateAllSessionsOfUser is NOT called; (2) for an
existing user with competitionContext=false assert that a team_membership row is
inserted, the function returns early with userJoined=true and
invitationSent=false, and updateAllSessionsOfUser is called. In each test,
create a pre-existing user and team, call the registration function (the code
path around db.insert(teamMembershipTable).values and updateAllSessionsOfUser),
assert DB state and return values, and add a single // `@lat`: [[section-id]]
comment immediately next to each test to reference its spec.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/wodsmith-start/src/server/registration.ts`:
- Around line 206-210: The current flow calls registerForCompetition() then
sendCompetitionTeamInviteEmail(), which allows email failures to leave persisted
team/membership/registration rows; wrap the multi-write registration logic in a
db.transaction() (use Drizzle ORM's db.transaction) inside
registerForCompetition()/the registration path so team, captain membership, and
registration are written atomically, then send or enqueue the invite email only
after the transaction commits; alternatively, if you prefer minimal changes,
catch errors from sendCompetitionTeamInviteEmail() here (where
competitionContext is handled), log them and continue (or queue a retry) so
delivery failures do not roll back or expose a partial persisted registration.

---

Nitpick comments:
In `@apps/wodsmith-start/src/server/registration.ts`:
- Around line 211-233: Add two regression tests covering the new branch split in
registration: (1) for an existing user with competitionContext=true assert that
an invitation is created (no team_membership row), the function returns
invitationSent=true (or appropriate shape), and updateAllSessionsOfUser is NOT
called; (2) for an existing user with competitionContext=false assert that a
team_membership row is inserted, the function returns early with userJoined=true
and invitationSent=false, and updateAllSessionsOfUser is called. In each test,
create a pre-existing user and team, call the registration function (the code
path around db.insert(teamMembershipTable).values and updateAllSessionsOfUser),
assert DB state and return values, and add a single // `@lat`: [[section-id]]
comment immediately next to each test to reference its spec.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 95aaaef4-9cd9-4425-8293-f0d8164690a6

📥 Commits

Reviewing files that changed from the base of the PR and between 9b68173 and ceb2e23.

📒 Files selected for processing (1)
  • apps/wodsmith-start/src/server/registration.ts

Comment on lines 206 to +210
if (competitionContext) {
// Get competition to find the competition_event team
const competition = await db.query.competitionsTable.findFirst({
where: eq(competitionsTable.id, competitionContext.competitionId),
// For competition invites, always create an invitation and send email
// so the user is notified and can complete registration requirements
// (questions, waivers). The acceptance flow handles adding them to the team.
// Fall through to the invitation creation path below.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t let invite-email failures unwind an already-persisted registration.

Routing existing users through the invite path here means a failure in sendCompetitionTeamInviteEmail() now bubbles out after registerForCompetition() has already inserted the team, captain membership, and registration rows. That leaves a partial registration behind and makes retries hit the duplicate team/registration guards. Catch delivery failures here or move the write set into a transaction and send/queue the email after commit.

As per coding guidelines, Use db.transaction() for multiple writes that need to be atomic in Drizzle ORM.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/wodsmith-start/src/server/registration.ts` around lines 206 - 210, The
current flow calls registerForCompetition() then
sendCompetitionTeamInviteEmail(), which allows email failures to leave persisted
team/membership/registration rows; wrap the multi-write registration logic in a
db.transaction() (use Drizzle ORM's db.transaction) inside
registerForCompetition()/the registration path so team, captain membership, and
registration are written atomically, then send or enqueue the invite email only
after the transaction commits; alternatively, if you prefer minimal changes,
catch errors from sendCompetitionTeamInviteEmail() here (where
competitionContext is handled), log them and continue (or queue a retry) so
delivery failures do not roll back or expose a partial persisted registration.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/wodsmith-start/src/server/registration.ts">

<violation number="1" location="apps/wodsmith-start/src/server/registration.ts:207">
P2: Existing users in competition contexts now fall through to the invitation-creation + email-sending path. If `sendCompetitionTeamInviteEmail()` throws after the invitation record (and any prior registration rows) have already been committed, the result is a partial state that cannot be retried cleanly because duplicate guards will reject the next attempt. Wrap the invitation insert and email send in a `db.transaction()`, or catch email delivery errors so they don't unwind the already-persisted data.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

// Get competition to find the competition_event team
const competition = await db.query.competitionsTable.findFirst({
where: eq(competitionsTable.id, competitionContext.competitionId),
// For competition invites, always create an invitation and send email
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Existing users in competition contexts now fall through to the invitation-creation + email-sending path. If sendCompetitionTeamInviteEmail() throws after the invitation record (and any prior registration rows) have already been committed, the result is a partial state that cannot be retried cleanly because duplicate guards will reject the next attempt. Wrap the invitation insert and email send in a db.transaction(), or catch email delivery errors so they don't unwind the already-persisted data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/server/registration.ts, line 207:

<comment>Existing users in competition contexts now fall through to the invitation-creation + email-sending path. If `sendCompetitionTeamInviteEmail()` throws after the invitation record (and any prior registration rows) have already been committed, the result is a partial state that cannot be retried cleanly because duplicate guards will reject the next attempt. Wrap the invitation insert and email send in a `db.transaction()`, or catch email delivery errors so they don't unwind the already-persisted data.</comment>

<file context>
@@ -203,51 +203,34 @@ async function inviteUserToTeamInternal({
-      // Get competition to find the competition_event team
-      const competition = await db.query.competitionsTable.findFirst({
-        where: eq(competitionsTable.id, competitionContext.competitionId),
+      // For competition invites, always create an invitation and send email
+      // so the user is notified and can complete registration requirements
+      // (questions, waivers). The acceptance flow handles adding them to the team.
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants