Skip to content

feat(analytics): add real-time active users tracking - #367

Open
Fury03 wants to merge 2 commits into
Deen-Bridge:mainfrom
Fury03:feat/issue-243-active-users
Open

feat(analytics): add real-time active users tracking#367
Fury03 wants to merge 2 commits into
Deen-Bridge:mainfrom
Fury03:feat/issue-243-active-users

Conversation

@Fury03

@Fury03 Fury03 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

1. Linked Issue

Closes #243

2. Problem Statement

There is no visibility into how many users are concurrently active, which makes capacity planning and peak-usage identification guesswork. Counting active users cannot be done with a local patch because it needs a shared, low-latency counter visible across app instances (Redis), a per-request activity signal from every authenticated route, and a configurable expiry so stale sessions fall out of the count.

3. Solution Comparison and Decision

  • Option A — Store last-active timestamps in MongoDB: Survives restarts but every request becomes a write to a hot collection and cross-instance pruning needs a background worker; the count is never truly real-time. Rejected.
  • Option B — In-memory Set on one Node process: Trivial and fast but wrong in a multi-instance deployment, and lost on restart. Rejected.
  • Option C — Redis sorted set (chosen): ZADD with a timestamp score gives O(log n) per-request updates, ZCARD after a range-prune gives an exact concurrent count, and the operation is atomic across instances. This is the only option that is both real-time and horizontally correct.

4. The Change

A ActiveUsersService over a Redis sorted set keyed by user id with a timestamp score:

async trackActivity({ userId }) {
  if (!userId || !this._isReady()) return 0;
  const now = Date.now();
  const timeoutMs = this.getTimeoutSeconds() * 1000;
  await client.zAdd(ACTIVE_USERS_KEY, [{ score: now, value: String(userId) }]);
  await client.zRemRangeByScore(ACTIVE_USERS_KEY, 0, now - timeoutMs);
  return 1;
}

Acceptance criteria mapped to behavior:

Acceptance criterion Entry point / behavior
Track concurrent active users in Redis activeUsersService.trackActivity — tuple (user id, timestamp) in a sorted set
Update activity per request via middleware trackActivity middleware, mounted globally in app.js; decodes the Bearer JWT (no DB hit) and writes fire-and-forget
Expire inactive sessions after configurable timeout ACTIVE_USER_TIMEOUT_SECONDS (default 300) pruned on every write/read
Endpoint to retrieve the current count GET /api/analytics/active-users (authenticated) returns { activeUsers, timeoutSeconds }

The middleware never blocks a request (fire-and-forget) and the service degrades to a no-op when Redis is unavailable, so analytics can never take the app down.

5. Compatibility Note (INTERFACE_VERSION)

No version bump. This adds a new /api/analytics/active-users endpoint and one opt-in .env var; it does not change any existing response shape or route.

6. Incidental Fixes

  • The global activity middleware only acts on requests carrying a valid Bearer token, so unauthenticated traffic is not charged a Redis write.
  • A fake-Redis client seam (setRedis/setTimeoutSeconds) lets the middleware→service→route chain be tested without a live Redis.

7. Testing

test/activeUsers.test.js drives the real exported app via supertest with a Redis-compatible fake injected at the client seam, so the middleware, service and endpoint path are all exercised:

  • requires authentication to read the active-user count
  • counts the requesting user via the activity middleware
  • counts each unique user once and ignores repeated activity
  • expires users who have been idle longer than the timeout
  • respects a shorter timeout via the environment override seam
  • returns 0 without error when Redis is unavailable

Result: Tests: 6 passed. A regression run of readingProgress and health suites (17 total) also passed, confirming the new global middleware does not break existing routes.

8. Additional Notes / Scope

One commit adding the middleware, service, controller, routes and test. It touches app.js only to mount the middleware and router; no other module is affected.

Track concurrent active users in Redis with a sliding inactivity window,
updated on every authenticated request via a global middleware, and expose
the current count through a new analytics endpoint.

- activeUsersService: Redis sorted-set tracking with configurable timeout
  (ACTIVE_USER_TIMEOUT_SECONDS) and graceful degradation when Redis is down
- activityTracker middleware: decodes the Bearer token and records activity
  fire-and-forget, wired globally in app.js
- GET /api/analytics/active-users endpoint (authenticated)
@drips-wave

drips-wave Bot commented Aug 29, 2026

Copy link
Copy Markdown

@Fury03 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 77faa5db-9264-4f6b-a197-1e80e036fa1b

📥 Commits

Reviewing files that changed from the base of the PR and between 2204417 and 30ee5aa.

📒 Files selected for processing (7)
  • .env.example
  • app.js
  • src/controllers/analytics/activeUsersController.js
  • src/middlewares/analytics/activityTracker.js
  • src/routes/analytics/activeUsersRoutes.js
  • src/services/analytics/activeUsersService.js
  • test/activeUsers.test.js

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.

@zeemscript

Copy link
Copy Markdown
Collaborator

@Fury03 this PR has merge conflicts with the main branch. Please resolve the conflicts (merge main in or rebase) and push the fix so it can be merged. Thanks!

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.

feat(analytics): Add real-time active users tracking

2 participants