Skip to content

feat(invitations): user-facing referral abilities, redemption event, referrer notification#3980

Merged
PierreBrisorgueil merged 3 commits into
masterfrom
feat/3945-invitations-user-facing
Jul 24, 2026
Merged

feat(invitations): user-facing referral abilities, redemption event, referrer notification#3980
PierreBrisorgueil merged 3 commits into
masterfrom
feat/3945-invitations-user-facing

Conversation

@PierreBrisorgueil

@PierreBrisorgueil PierreBrisorgueil commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

The invitations module ships a referral reward path, but invitationAbilities only granted manage all to platform admins — regular authenticated users had no ability on Invitation, so a user-facing referrals surface was unreachable. Groundwork (invitedBy-scoped list(), self-invite guard) shipped earlier.

  • Widen invitationAbilities behind a new config.invitations.userFacing flag (stack default OFF, preserving current admin-only behavior for every existing deployment): authenticated users can create an Invitation and read invitations they sent — the real invitedBy-scoping is enforced in InvitationsService.list() (both grants are collection-level CASL rules; no per-document Invitation subject is registered, and the reasoning for that is documented inline).
  • resend (POST /invitations/:id/resend) stays explicitly admin-gated in the controller, independent of the flag: its POST verb maps to the same CASL create action as new-invitation creation, so a naive widen would otherwise let any user resend someone else's pending invite. remove/revoke needed no change (non-admins are never granted delete).
  • Analytics: emit an invitation_redeemed event on accept, and carry invited / invitationId / invitedBy attribution on the user_signed_up event.
  • Referrer notification: send a best-effort email (existing mailer abstraction, new referral-reward-earned template) to the referrer when their referral reward is freshly credited — fires exactly once per invitation (piggybacks on the existing atomic ledger dedup guard), so a reconcile-cron back-fill or a replayed event never double-sends.
  • Index users.referredBy (supports the "who did I refer" query).

Stack-generic: no project-specific enums/names/amounts; reward amounts stay config-driven as before; tests use generic mock data.

Test plan

  • npm run lint — clean
  • npm run test:coverage -- --testPathPatterns='unit' — 164 suites / 2226 tests passed, coverage threshold holds (integration/E2E skipped locally, no MongoDB available in this environment — CI runs the full suite)
  • New/updated unit tests: invitationAbilities for both flag states + admin-always-wins; resend's admin-only gate (3 rejection cases); accept()'s invitation_redeemed capture (incl. best-effort swallow); user_signed_up invite/referral properties (invited + non-invited paths); notifyReferrer (mailer off, freshly-applied grant, non-applied grant, duplicate replay, missing email, send failure)
  • CI green on this PR
  • CodeRabbit — zero unresolved threads

Closes #3945

Summary by CodeRabbit

  • New Features

    • Added referral reward emails for inviters when rewards are successfully credited.
    • Added referral and invitation activity tracking for accepted invitations and new signups.
    • Added configurable access for authenticated users to create and view invitations.
  • Bug Fixes

    • Invitation creation is now rate-limited, including legacy routes.
    • Resending invitations is restricted to administrators.
    • Invitation email errors now return a safe, consistent message without exposing provider details.
    • Improved performance for referral lookups.
  • Documentation

    • Updated referral rollout and invitation access documentation.

…referrer notification

Widen invitationAbilities behind a new config.invitations.userFacing flag
(default OFF, admin-only preserved): authenticated users can create their
own invitation and read invitations they sent (server-scoped via the
existing InvitationsService.list() invitedBy scoping). resend stays
explicitly admin-gated in the controller since its POST verb maps to the
same CASL 'create' action as new-invitation creation.

Also: emit an invitation_redeemed analytics event on accept, carry
invite/referral attribution on the signup event, notify the referrer by
email (existing mailer abstraction) when their referral reward is freshly
credited, and index users.referredBy.

Closes #3945
… gate)

Widening invitationAbilities to authenticated users makes an unbounded
POST /api/invitations a DB-bloat / outbound-email-spam abuse surface
(caught by the pre-push critical-review gate). Add an invitationsCreate
rate-limit profile (base-layer pattern, active under every NODE_ENV,
stricter cap in production) on both the canonical route and the
/api/auth/invitations alias, which points at the same controller.

Also: move mailer.isConfigured() inside notifyReferrer's try/catch so a
synchronous throw from the check itself can't escape.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PierreBrisorgueil, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ef6d0c93-8969-4d14-b40b-7dc5fe5163ab

📥 Commits

Reviewing files that changed from the base of the PR and between 92c0244 and 4d63fff.

📒 Files selected for processing (1)
  • config/templates/referral-reward-earned.html

Walkthrough

This PR enables config-gated user-facing invitation access, adds creation throttles to canonical and legacy routes, enforces admin-only resend behavior, records invitation attribution analytics, emails referrers after fresh rewards, and indexes User.referredBy.

Changes

Referral invitation rollout

Layer / File(s) Summary
Invitation access and throttling
modules/invitations/config/*, config/defaults/*, modules/invitations/policies/*, modules/invitations/routes/*, modules/auth/routes/*, lib/middlewares/tests/*
Adds the userFacing access flag, invitation creation rate-limit profiles, limiter middleware on canonical and legacy routes, conditional non-admin permissions, and base-layer limiter coverage.
Admin-only resend handling
modules/invitations/controllers/*, modules/invitations/tests/invitations.controller.unit.tests.js
Adds an explicit admin check for resend and returns generic 422 responses for unclassified transport failures while preserving mapped status handling.
Invitation and signup analytics
modules/invitations/services/*, modules/invitations/tests/invitations.service.unit.tests.js, modules/auth/controllers/*, modules/auth/tests/*
Captures invitation_redeemed on successful acceptance and adds invitation attribution fields to user_signed_up analytics events.
Referral reward notification and indexing
modules/billing/*, config/templates/*, modules/users/models/*, modules/invitations/README.md
Sends best-effort reward emails after newly applied referrer credits, adds the email template, indexes referredBy, and documents the rollout behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Signup
  participant InvitationService
  participant AuthController
  participant AnalyticsService
  Signup->>InvitationService: accept invitation
  InvitationService->>AnalyticsService: capture invitation_redeemed
  Signup->>AuthController: complete account creation
  AuthController->>AnalyticsService: capture user_signed_up attribution
Loading
sequenceDiagram
  participant InvitationService
  participant BillingReferralService
  participant UserService
  participant Mailer
  InvitationService->>BillingReferralService: grant referral reward
  BillingReferralService->>UserService: load referrer
  BillingReferralService->>Mailer: send referral reward email
Loading

Possibly related PRs

🚥 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main referral-feature changes and stays specific to the PR.
Description check ✅ Passed The description covers the main changes, rationale, issue link, and testing well enough, though some template sections are missing.
Linked Issues check ✅ Passed The PR implements the #3945 requirements: user-facing abilities, redemption analytics, referrer email, and the referredBy index.
Out of Scope Changes check ✅ Passed The added rate limiting, tests, docs, and template changes are supporting the referral feature and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/3945-invitations-user-facing

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.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.15%. Comparing base (d34528c) to head (4d63fff).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3980      +/-   ##
==========================================
+ Coverage   93.12%   93.15%   +0.02%     
==========================================
  Files         170      170              
  Lines        5615     5639      +24     
  Branches     1809     1817       +8     
==========================================
+ Hits         5229     5253      +24     
  Misses        311      311              
  Partials       75       75              
Flag Coverage Δ
integration 61.30% <68.00%> (+0.02%) ⬆️
unit 75.22% <92.00%> (+0.10%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d34528c...4d63fff. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/templates/referral-reward-earned.html`:
- Line 4: Replace the empty title element in the referral reward earned template
with a non-empty title that combines the configured app name and the referral
reward context, preserving the template’s existing variable conventions.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 6979157b-234b-4ed1-bd99-28857d696c98

📥 Commits

Reviewing files that changed from the base of the PR and between d34528c and 92c0244.

📒 Files selected for processing (19)
  • config/defaults/production.config.js
  • config/defaults/test.config.js
  • config/templates/referral-reward-earned.html
  • lib/middlewares/tests/rateLimiter.baseLayer.unit.tests.js
  • modules/auth/controllers/auth.controller.js
  • modules/auth/routes/auth.routes.js
  • modules/auth/tests/auth.silent.catch.unit.tests.js
  • modules/billing/services/billing.referral.service.js
  • modules/billing/tests/billing.referral.service.unit.tests.js
  • modules/invitations/README.md
  • modules/invitations/config/invitations.development.config.js
  • modules/invitations/controllers/invitations.controller.js
  • modules/invitations/policies/invitations.policy.js
  • modules/invitations/routes/invitations.routes.js
  • modules/invitations/services/invitations.service.js
  • modules/invitations/tests/invitations.controller.unit.tests.js
  • modules/invitations/tests/invitations.policy.unit.tests.js
  • modules/invitations/tests/invitations.service.unit.tests.js
  • modules/users/models/users.model.mongoose.js

Comment thread config/templates/referral-reward-earned.html Outdated
CodeRabbit (HTMLHint): title-require. Mirrors the existing app-name pattern
used elsewhere in this template's own copy.
@PierreBrisorgueil
PierreBrisorgueil merged commit 223ca9e into master Jul 24, 2026
8 checks passed
@PierreBrisorgueil
PierreBrisorgueil deleted the feat/3945-invitations-user-facing branch July 24, 2026 18:38
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.

✨ User-facing referral invitations: widen abilities beyond platform admins

1 participant