Skip to content

feat(contributors): add manual tier upgrade for enrolled champions#3121

Merged
bturcotte520 merged 3 commits intomainfrom
addUpgrade
May 8, 2026
Merged

feat(contributors): add manual tier upgrade for enrolled champions#3121
bturcotte520 merged 3 commits intomainfrom
addUpgrade

Conversation

@bturcotte520
Copy link
Copy Markdown
Contributor

Allow existing champions to be upgraded

@bturcotte520 bturcotte520 requested a review from johnnyeric May 7, 2026 21:27
// Prefer the explicit membership link over the email-derived match, same as enrollContributorChampion.
const linkedKiloUserId = row.linkedKiloUserId ?? row.linkedUserId;

const creditGranted = await db.transaction(async tx => {
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.

WARNING: Concurrent upgrades can double-grant top-up credits

The current tier and credit differential are computed before the transaction acquires the FOR UPDATE lock. If two admins submit the same upgrade concurrently, both requests can read the old tier, then serialize at this lock and each grant the same differential. Re-read the membership/current tier after acquiring the lock (or condition the update on the old tier) before computing/granting the top-up.

counts_as_selfservice: false,
dbOrTx: tx,
});
granted = result.success;
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.

WARNING: Successful upgrade top-ups do not advance the grant timestamp

When a contributor tier with no prior credits, such as contributor, is manually upgraded and this call grants the immediate top-up, credits_last_granted_at stays NULL. For memberships with linked_kilo_user_id set, the next credit refresh sees the row as due immediately and can grant the new monthly amount again. Mirror enrollment by setting credits_last_granted_at when the top-up succeeds.

@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented May 7, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • apps/web/src/app/admin/contributors/page.tsx
  • apps/web/src/lib/contributor-champions/service.ts
  • apps/web/src/routers/admin/contributor-champions-router.ts

Reviewed by gpt-5.5-2026-04-23 · 815,973 tokens

if (!membership?.enrolled_tier) throw new Error('Contributor membership not found');

const lockedCurrentCreditUsd =
TIER_CREDIT_USD[membership.enrolled_tier as ContributorTier] ?? 0;
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.

Suggest to use parseContributorTier for safety in case enrolled_tier has invalid data.
e.g.

const lockedCurrentTier = parseContributorTier(membership.enrolled_tier);
if (!lockedCurrentTier) throw new Error(`Invalid enrolled_tier "${membership.enrolled_tier}" in DB`);                                                                  
const lockedCurrentCreditUsd = TIER_CREDIT_USD[lockedCurrentTier];  

counts_as_selfservice: false,
dbOrTx: tx,
});
granted = result.success;
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.

The bot's concern has been addressed about the positive scenario, but if the grant fails somethings would still get committed in the transaction. I'd suggest to reject transaction in case of grant failure so that it can be retried cleanly.
suggestion (replacing current granted = result.success :

if (!result.success) {                                                                                                                     
  throw new Error('Failed to grant top-up credit; rolling back tier upgrade');                                                                                       
}
granted = true;

.where(eq(contributor_champion_memberships.contributor_id, input.contributorId))
.limit(1);

if (!membership?.enrolled_tier) throw new Error('Contributor membership not found');
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.

Would it make sense to separate the error logs for missing membership or not enrolled?
like:

if (!membership) throw new Error('Contributor membership not found');                                                                                                  
if (!membership.enrolled_tier) throw new Error('Contributor is not currently enrolled');  

@bturcotte520 bturcotte520 merged commit 0abd6e2 into main May 8, 2026
17 checks passed
@bturcotte520 bturcotte520 deleted the addUpgrade branch May 8, 2026 15:43
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