Skip to content

feat: align members table 'Last active' with active user billing definition#28003

Draft
sean-brydon wants to merge 2 commits intomainfrom
devin/1771327790-align-last-active-with-billing
Draft

feat: align members table 'Last active' with active user billing definition#28003
sean-brydon wants to merge 2 commits intomainfrom
devin/1771327790-align-last-active-with-billing

Conversation

@sean-brydon
Copy link
Member

@sean-brydon sean-brydon commented Feb 17, 2026

What does this PR do?

The "Last active" column in team/org members tables previously used User.lastActiveAt, which only tracked when a user last hosted a new booking. This didn't match the active user billing definition, where "active" means the user hosted or attended at least one ACCEPTED booking.

This PR replaces the static DB field with a computed value that queries the most recent accepted booking where the user was either host or attendee, cached via the @Memoize Redis decorator.

Changes:

  • Adds getLastActiveAt(userId, email) to ActiveUserBillingRepository — parallel queries for latest accepted booking as host + attendee, returns the more recent
  • Creates CachedActiveUserBillingRepository wrapping the method with @Memoize (cache key: active-user-billing:lastActive:{userId}, default 5min TTL)
  • Wires up DI (tokens, module, container)
  • Updates teams/listMembers.handler.ts and organizations/listMembers.handler.ts to use the cached repository instead of User.lastActiveAt
  • Removes lastActiveAt from the Prisma select in both handlers since it's now computed
  • Adds 8 unit tests for the new repository method
  • Updates existing ActiveUserBillingService test mock to include the new method

Link to Devin run: https://app.devin.ai/sessions/ac1b1321f14f4d6d93342a73ac3b30b8
Requested by: @sean-brydon


Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A — internal billing logic, no user-facing docs needed.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Setup: Create an org/team with members who have:
    • Only hosted accepted bookings
    • Only attended accepted bookings (as attendee)
    • Both hosted and attended
    • No accepted bookings
  2. Expected: "Last active" column should show the most recent accepted booking date (host or attendee), formatted in user's locale/timezone
  3. Cache verification: Check Redis for keys like active-user-billing:lastActive:{userId} after loading members list

Environment variables: None required beyond standard Cal.com setup


Human Review Checklist

Critical:

  • Filter mismatch in org handler: The lastActiveAtFilter (~line 121 in organizations/listMembers.handler.ts) still builds a Prisma where clause on User.lastActiveAt (the old DB column), but the displayed value now uses the new computed logic. Filtering by "Last active" date range will return results based on the old definition. Should this filter be removed, or is a follow-up needed to reconcile it?

Important:

  • No time-window constraint: Unlike the billing service (which checks bookings within a billing period), getLastActiveAt queries the most recent accepted booking ever. This is intentional for "Last active" display, but worth confirming this is the desired behavior.
  • Performance on cold cache: Each member triggers 2 Prisma queries (host + attendee) on cache miss. For large member lists (e.g., 100 members), that's 200 queries on first load. After 5min TTL, the cache refreshes. Is this acceptable, or should we batch/optimize?
  • Cache key strategy: The cache key is active-user-billing:lastActive:{userId} (email not included). If a user changes email, stale attendee data is served until TTL expires (5min). Is this acceptable?
  • Dead code: User.lastActiveAt is still being written by RegularBookingService but is no longer read by these handlers. Should it be deprecated/removed in a follow-up?

Nice to have:

  • Consider adding integration tests for the handler changes (currently only repository unit tests exist)
  • Verify the @Memoize decorator's default 5min TTL is appropriate for this use case

Checklist

  • I have read the contributing guide
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have checked if my changes generate no new warnings
  • My PR is appropriately sized (253 lines, 9 files)

…nition

Replace User.lastActiveAt (only tracks hosting) with computed last active
date from bookings, matching the billing definition where 'active' means
hosted or attended at least one ACCEPTED booking.

Changes:
- Add getLastActiveAt to ActiveUserBillingRepository (queries most recent
  accepted booking as host or attendee)
- Create CachedActiveUserBillingRepository with @memoize for Redis caching
- Wire up DI (tokens, module, container)
- Update teams and orgs listMembers handlers to use new logic
- Add unit tests for the new repository method

Co-Authored-By: sean@cal.com <Sean@brydon.io>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Co-Authored-By: sean@cal.com <Sean@brydon.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments