Skip to content

Metrics Endpoint Speed#966

Merged
BilalG1 merged 20 commits intodevfrom
perf/user-list
Nov 6, 2025
Merged

Metrics Endpoint Speed#966
BilalG1 merged 20 commits intodevfrom
perf/user-list

Conversation

@BilalG1
Copy link
Contributor

@BilalG1 BilalG1 commented Oct 20, 2025

Screenshot 2025-10-20 at 11 23 10 AM Screenshot 2025-10-20 at 11 24 00 AM

High-level PR Summary

This PR optimizes the performance of user list and metrics endpoints by refactoring SQL queries to use more efficient patterns. The changes include rewriting queries to use LATERAL joins and CTEs with proper filtering, extracting common user mapping logic into reusable functions, and adding performance tests with SQL scripts to generate realistic test data (10,000 mock users and activity events across 100 countries).

⏱️ Estimated Review Time: 30-90 minutes

💡 Review Order Suggestion
Order File Path
1 apps/e2e/tests/backend/performance/mock-users.sql
2 apps/e2e/tests/backend/performance/mock-metric-events.sql
3 apps/e2e/tests/backend/performance/users-list.test.ts
4 apps/backend/src/app/api/latest/users/crud.tsx
5 apps/backend/src/app/api/latest/internal/metrics/route.tsx

Need help? Join our Discord

Analyze latest changes


Important

Optimize metrics and user list endpoints with SQL refactoring, caching, and performance tests, adding a CacheEntry model and mock data scripts.

  • Performance Optimization:
    • Refactor SQL queries in route.tsx to use LATERAL joins and CTEs for efficient data retrieval.
    • Implement caching in route.tsx using getOrSetCacheValue() to reduce database load.
  • Database Changes:
    • Add CacheEntry model to schema.prisma and create corresponding table and index in migration.sql.
    • Remove auto-migration metadata step from check-prisma-migrations.yaml.
  • Testing:
    • Add performance tests in metrics.test.ts to benchmark metrics and user endpoints.
    • Create mock data scripts mock-users.sql and mock-metric-events.sql for testing with 10,000 users and events across 100 countries.
  • Miscellaneous:
    • Update db-migrations.ts to include new migration file generation logic.
    • Add cache.tsx for caching logic implementation.

This description was created by Ellipsis for 4d9be71. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Metrics now use a cache layer with per-entry TTL and tenancy-aware loaders.
  • Bug Fixes

    • Improved accuracy of daily active and related metrics with tenancy-aware counting and more robust last-active computation.
  • Performance

    • Faster metrics responses via batched reads and cache-backed endpoints.
  • Tests

    • Added end-to-end performance benchmarks and SQL seed scripts for metrics/user load testing.
  • Chores

    • DB migration added support for cached entries; CI migration check flow adjusted; migration tooling improved.

@vercel
Copy link

vercel bot commented Oct 20, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Nov 6, 2025 0:03am
stack-dashboard Ready Ready Preview Comment Nov 6, 2025 0:03am
stack-demo Ready Ready Preview Comment Nov 6, 2025 0:03am
stack-docs Ready Ready Preview Comment Nov 6, 2025 0:03am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 20, 2025

Warning

Rate limit exceeded

@BilalG1 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 18 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 45ca5b3 and 9e0e62f.

📒 Files selected for processing (3)
  • apps/backend/prisma/schema.prisma (2 hunks)
  • apps/backend/src/app/api/latest/users/crud.tsx (2 hunks)
  • apps/backend/src/lib/cache.tsx (1 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a CacheEntry DB model and a getOrSetCacheValue helper, wraps several internal metrics loaders with a tenancy-scoped caching layer, replaces per-user reads with batched Prisma queries and CRUD mapping, refactors user last-active mapping, and adds E2E performance tests and SQL seed data for benchmarking.

Changes

Cohort / File(s) Summary
Metrics route & tenancy-aware loaders
apps/backend/src/app/api/latest/internal/metrics/route.tsx
Adds cache wrapper usage (withMetricsCache) around metric loaders, rewrites loaders to be tenancy-aware, accepts Prisma transactions where needed, and replaces per-user adminRead loops with batched Prisma fetches and mapping to CRUD shapes.
Cache table & runtime helper
apps/backend/prisma/migrations/20251020210214_add_cache_entry/migration.sql, apps/backend/prisma/schema.prisma, apps/backend/src/lib/cache.tsx
Adds CacheEntry table and Prisma model (namespace+cacheKey unique), and implements getOrSetCacheValue with TTL validation, expiry computation, loader invocation, and upsert semantics.
User CRUD refactor
apps/backend/src/app/api/latest/users/crud.tsx
Extracts mapUserLastActiveAtMillis to centralize lastActiveAt computation and refactors getUsersLastActiveAtMillis to use it.
Batching recently-active users
apps/backend/src/app/api/latest/internal/metrics/route.tsx (same file)
Fetches recent event user IDs, batch-loads users with userFullInclude, maps Prisma rows to CRUD objects via userPrismaToCrud, and removes per-user error handling.
E2E perf tests & SQL seeds
apps/e2e/tests/backend/performance/metrics.test.ts, apps/e2e/tests/backend/performance/mock-metric-events.sql, apps/e2e/tests/backend/performance/mock-users.sql
Adds skipped performance test suites and two SQL scripts to generate ~10k users and synthetic event data across many countries for benchmarking.
Migration tooling & CI workflow
apps/backend/scripts/db-migrations.ts, .github/workflows/check-prisma-migrations.yaml
Replaces execSync with spawnSync, adds interactive migration generation and apply flow, and removes the workflow step that dropped SchemaMigration metadata.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant MetricsRoute
    participant CacheLayer as Cache (CacheEntry)
    participant EventsDB as Events
    participant Prisma
    participant Mapper

    Client->>MetricsRoute: GET /internal/metrics
    MetricsRoute->>CacheLayer: get cached payload (namespace, key)
    alt cache hit & valid
        CacheLayer-->>MetricsRoute: cached payload
        MetricsRoute-->>Client: Return cached metrics
    else cache miss/expired
        MetricsRoute->>EventsDB: Query recent events (tenancy-scoped)
        EventsDB-->>MetricsRoute: event rows (userIds)
        alt events found
            MetricsRoute->>Prisma: Batch fetch users by IDs (userFullInclude)
            Prisma-->>MetricsRoute: Prisma user records
            MetricsRoute->>Mapper: userPrismaToCrud mapping
            Mapper-->>MetricsRoute: CRUD-shaped users
            MetricsRoute->>CacheLayer: upsert payload with expiresAt
            CacheLayer-->>MetricsRoute: upsert OK
            MetricsRoute-->>Client: Return computed metrics
        else no events
            MetricsRoute-->>Client: Return empty result
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • fix metrics queries #880 — Touches the same metrics route and related loader changes; likely overlaps with tenancy-aware and batching modifications.

Suggested reviewers

  • N2D4

Poem

"I nibble rows and stitch the cache so neat,
Batching hops of users with tiny feet,
Tenancy burrows tidy each trace,
Seeds and tests set the metrics race,
Hop—metrics bloom in a rabbit beat." 🐰

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Metrics Endpoint Speed" is specific and directly related to a major component of the changeset. The raw_summary confirms that the metrics endpoint (apps/backend/src/app/api/latest/internal/metrics/route.tsx) has been optimized with caching via withMetricsCache and tenancy-aware query refactoring. While the PR also includes significant changes to user list/CRUD functionality, caching infrastructure, database schema, and test infrastructure, the title accurately captures the primary performance optimization objective mentioned in the PR objectives summary ("Metrics Endpoint Speed"). The title is concise, clear, and not vague or generic, properly communicating the core purpose of the changes.
Description Check ✅ Passed The PR description substantially exceeds the minimal repository template requirements, which only asks to confirm reading of CONTRIBUTING.md guidelines. The provided description includes: two performance comparison screenshots, a high-level summary clearly explaining the optimization work, an estimated review time (30-90 minutes), a well-organized review order table, an IMPORTANT section with detailed bullet-pointed changes, and links to resources. The description is complete, well-structured, and specific—covering caching implementation, SQL refactoring, database schema changes, testing infrastructure, and miscellaneous updates without vagueness or omissions.

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.

Copy link

@recurseml recurseml bot left a comment

Choose a reason for hiding this comment

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

Review by RecurseML

🔍 Review performed on 8c805a8..463a7d2

✨ No bugs found, your code is sparkling clean

✅ Files analyzed, no issues (5)

apps/backend/src/app/api/latest/internal/metrics/route.tsx
apps/backend/src/app/api/latest/users/crud.tsx
apps/e2e/tests/backend/performance/mock-metric-events.sql
apps/e2e/tests/backend/performance/mock-users.sql
apps/e2e/tests/backend/performance/users-list.test.ts

@BilalG1 BilalG1 changed the title Perf/user list Metrics Endpoint Speed Oct 20, 2025
@BilalG1 BilalG1 marked this pull request as ready for review October 20, 2025 18:32
@claude
Copy link
Contributor

claude bot commented Oct 20, 2025

Claude finished @BilalG1's task —— View job


PR Review: Metrics Endpoint Speed

I've completed my review of this performance optimization PR. Here's my comprehensive feedback:

📊 Overall Assessment

This is a well-executed performance optimization PR with solid technical improvements. The changes demonstrate good database optimization practices and thorough performance testing setup.

Strengths

SQL Query Optimizations:

  • Excellent use of LATERAL joins in loadUsersByCountry() (lines 33-46) - this is a proper optimization for getting the latest record per user
  • Good use of CTEs to break down complex queries into readable chunks
  • Proper indexing considerations with filtered queries
  • Smart use of DISTINCT ON for deduplication in loadRecentlyActiveUsers()

Performance Testing Infrastructure:

  • Comprehensive mock data generation (10K users across 100 countries)
  • Realistic test data with proper temporal distribution
  • Good separation of concerns between user creation and event generation
  • Performance tests include warmup runs and multiple iterations

Code Quality:

  • Clean separation of data loading functions
  • Proper error handling and null checks
  • Good use of TypeScript types and schema validation

⚠️ Areas of Concern

Security & Data Validation:

  1. SQL Injection Risk - While using parameterized queries, the sqlQuoteIdent() usage should be audited:

    // apps/backend/src/app/api/latest/internal/metrics/route.tsx:24
    FROM ${sqlQuoteIdent(schema)}."ProjectUser" pu

    Ensure sqlQuoteIdent() properly sanitizes schema names.

  2. Anonymous User Filtering - Multiple places use includeAnonymous parameter but the boolean conversion needs validation:

    // Line 111: Could be vulnerable if includeAnonymous is manipulated
    AND (${includeAnonymous} OR COALESCE("data"->>'isAnonymous', 'false') != 'true')

Performance Considerations:

  1. Memory Usage - loadRecentlyActiveUsers() loads full user objects with userFullInclude. For high user counts, this could be memory intensive.

  2. Query Complexity - Some queries are very complex (e.g., loadUsersByCountry with LATERAL joins). Consider adding query explain plans to documentation.

  3. N+1 Query Pattern - In loadRecentlyActiveUsers(), there's a potential N+1 pattern where events are fetched first, then users are fetched separately.

🐛 Potential Bugs

  1. Type Coercion Issue:

    // apps/backend/src/app/api/latest/internal/metrics/route.tsx:56
    Number(userCount) // bigint to number conversion could lose precision
  2. Date Handling:

    // Line 86: Timezone issues possible
    date: x.date.toISOString().split('T')[0]
  3. Null Handling:

    // Line 204: Could return null users in array
    const user = dbUsers.find((user) => user.projectUserId === event.userId);
    return user ? userPrismaToCrud(user, event.lastActiveAt.getTime()) : null;

🚀 Performance Recommendations

  1. Add Database Indexes: Consider adding these indexes for better performance:

    CREATE INDEX CONCURRENTLY idx_event_user_activity_project 
    ON "Event"("data"->>'projectId', "data"->>'branchId', "eventStartedAt") 
    WHERE '$user-activity' = ANY("systemEventTypeIds");
  2. Query Optimization: Consider using window functions instead of LATERAL joins where possible for better performance.

  3. Caching: For metrics that don't need real-time data, consider adding Redis caching with appropriate TTL.

🧪 Test Coverage Assessment

Good:

  • Performance tests with realistic data volumes
  • Multiple test iterations for consistency
  • Proper test isolation with describe.skip

Missing:

  • Error condition testing (what happens with malformed data?)
  • Edge case testing (empty datasets, extreme date ranges)
  • Security testing (malicious query parameters)

📝 Recommendations

  1. Add input validation for all query parameters
  2. Add database explain plans to documentation for complex queries
  3. Consider pagination for loadRecentlyActiveUsers() if user count grows large
  4. Add monitoring/alerting for query performance in production
  5. Document the performance improvements with before/after metrics

Minor Issues

  • Line 77: cumUsers is calculated but not used in the return value
  • Consider extracting magic numbers (like LIMIT 5) to constants
  • Some queries could benefit from better commenting for maintainability

🎯 Security Score: 7/10

The code is generally secure but needs input validation improvements and SQL injection auditing.

🏃♂️ Performance Score: 8/10

Excellent optimizations with LATERAL joins and CTEs. Minor concerns about memory usage and complex query patterns.

📋 Code Quality Score: 8/10

Well-structured, readable code with good separation of concerns. TypeScript usage is solid.


This PR represents a significant improvement to the metrics endpoint performance. The technical approach is sound, and the performance testing infrastructure is excellent. Address the security concerns and consider the performance recommendations for a production-ready solution.

@BilalG1 BilalG1 requested a review from N2D4 October 20, 2025 18:34
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Oct 20, 2025
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Summary

This PR optimizes the metrics endpoint query performance by refactoring SQL queries to use more efficient patterns. Key changes include:

  • Daily Active Users query: Restructured to use CTEs with proper filtering and deduplication at the SQL level, avoiding DISTINCT on computed expressions
  • Recently Active Users: Replaced N+1 pattern (iterating through events and calling usersCrudHandlers.adminRead for each) with a batch query approach using findMany and in-memory mapping
  • User mapping logic: Extracted common pattern into reusable mapUserLastActiveAtMillis function using Map<string, number> for O(1) lookups
  • Test infrastructure: Added comprehensive SQL scripts to generate 10k test users and 300k activity events across 100 countries for realistic performance testing

The optimizations focus on reducing database round trips and improving query efficiency through better use of PostgreSQL features like CTEs and proper grouping.

Confidence Score: 4/5

  • This PR is safe to merge with low risk - query optimizations are well-structured and maintain correctness
  • Score reflects solid SQL optimization work with good test infrastructure. Minor deduction for using plain object instead of Map in one location and lack of index verification for query performance
  • apps/backend/src/app/api/latest/internal/metrics/route.tsx - verify database indexes support the LATERAL join patterns

Important Files Changed

File Analysis

Filename Score Overview
apps/backend/src/app/api/latest/internal/metrics/route.tsx 4/5 Refactored SQL queries for better performance using CTEs and extracted user mapping logic, improving Daily Active Users query deduplication
apps/backend/src/app/api/latest/users/crud.tsx 5/5 Extracted user mapping logic into reusable mapUserLastActiveAtMillis function using Map for efficient lookups
apps/e2e/tests/backend/performance/metrics.test.ts 5/5 Added skipped performance tests with warmup and multiple iterations for metrics endpoint benchmarking

Sequence Diagram

sequenceDiagram
    participant Client
    participant MetricsRoute as Metrics Route Handler
    participant PrismaClient as Prisma Client
    participant PostgreSQL as PostgreSQL DB
    
    Client->>MetricsRoute: GET /api/v1/internal/metrics
    
    par Parallel Metric Queries
        MetricsRoute->>PrismaClient: Count total users
        PrismaClient->>PostgreSQL: SELECT COUNT(*) FROM ProjectUser
        PostgreSQL-->>PrismaClient: Count result
        PrismaClient-->>MetricsRoute: Total users
    and
        MetricsRoute->>PrismaClient: Load daily active users
        PrismaClient->>PostgreSQL: CTE query with filtered_events + unique_daily_users
        Note over PostgreSQL: Deduplicates (day, userId) pairs<br/>before aggregation
        PostgreSQL-->>PrismaClient: DAU data
        PrismaClient-->>MetricsRoute: Daily active users
    and
        MetricsRoute->>PrismaClient: Load users by country
        PrismaClient->>PostgreSQL: LATERAL join for latest country per user
        Note over PostgreSQL: Efficient per-user lookup<br/>with LIMIT 1
        PostgreSQL-->>PrismaClient: Country distribution
        PrismaClient-->>MetricsRoute: Users by country
    and
        MetricsRoute->>PrismaClient: Load recently active users
        PrismaClient->>PostgreSQL: DISTINCT ON (userId) for latest activity
        PostgreSQL-->>PrismaClient: Top 5 user IDs + timestamps
        MetricsRoute->>PrismaClient: Batch findMany for user details
        PrismaClient->>PostgreSQL: SELECT * FROM ProjectUser WHERE id IN (...)
        PostgreSQL-->>PrismaClient: User records
        Note over MetricsRoute: Map users with lastActiveAt<br/>in memory
        PrismaClient-->>MetricsRoute: Recently active users
    end
    
    MetricsRoute-->>Client: Aggregated metrics response
Loading

Additional Comments (1)

  1. apps/backend/src/app/api/latest/internal/metrics/route.tsx, line 55-59 (link)

    syntax: use Map<string, number> instead of plain object for dynamic keys to avoid prototype pollution vulnerabilities

    Context Used: Rule from dashboard - Use Map<A, B> instead of plain objects when using dynamic keys to avoid prototype pollution vulnerab... (source)

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link
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 (9)
apps/backend/src/app/api/latest/users/crud.tsx (1)

197-216: Add a precondition to guard array alignment; keep fallback robust.

mapUserLastActiveAtMillis assumes userIds and userSignedUpAtMillis are index-aligned. Add an assertion to fail fast on mismatch.

 const mapUserLastActiveAtMillis = (
   events: Array<{ userId: string, lastActiveAt: Date }>,
   userIds: string[],
   userSignedUpAtMillis: (number | Date)[],
 ): number[] => {
+  if (userIds.length !== userSignedUpAtMillis.length) {
+    throw new StackAssertionError(
+      "userIds and userSignedUpAtMillis must have identical length",
+      { userIdsLen: userIds.length, signedUpLen: userSignedUpAtMillis.length }
+    );
+  }
   const eventsByUserId = new Map<string, number>();
   for (const event of events) {
     eventsByUserId.set(event.userId, event.lastActiveAt.getTime());
   }
apps/e2e/tests/backend/performance/mock-users.sql (1)

24-39: Consider idempotency guards to make the seed re-runnable.

Re-running will create more users and auth rows. If that’s undesirable for perf runs, add ON CONFLICT DO NOTHING keyed on deterministic IDs or wrap in a DELETE-by-tag prelude. Example patterns:

  • Add a seed tag (e.g., displayName prefix) and DELETE FROM ... WHERE displayName LIKE 'Perf Test User %' before inserts.
  • Or generate deterministic UUIDs from idx (uuid_generate_v5) and use ON CONFLICT DO NOTHING.

Also applies to: 41-57, 58-79

apps/backend/src/app/api/latest/internal/metrics/route.tsx (2)

92-129: DAU CTE structure looks correct; add indexes to keep it fast.

For sustained performance on large Event volumes, ensure functional indexes exist:

  • GIN on "systemEventTypeIds"
  • BTREE on ("eventStartedAt")
  • BTREE on ((data->>'projectId')), ((COALESCE(data->>'branchId','main'))), ((data->>'userId'))
  • Partial index for '$user-activity' filter if common

Optional: tighten the time window by passing a truncated date (new Date().toISOString().slice(0,10)) to avoid TZ ambiguity between app and DB.


164-207: Nice N+1 removal; minor O(1) lookup improvement.

Build a Map for dbUsers to avoid linear finds (future-proof if LIMIT grows).

-  const userObjects = events.map((event) => {
-    const user = dbUsers.find((user) => user.projectUserId === event.userId);
-    return user ? userPrismaToCrud(user, event.lastActiveAt.getTime()) : null;
-  });
+  const byId = new Map(dbUsers.map(u => [u.projectUserId, u]));
+  const userObjects = events.map((event) => {
+    const user = byId.get(event.userId);
+    return user ? userPrismaToCrud(user, event.lastActiveAt.getTime()) : null;
+  });
apps/e2e/tests/backend/performance/metrics.test.ts (3)

1-5: Imports OK; consider removing unused types to keep lint happy.

If User is unused, drop it to reduce noise.


6-23: Gate perf suite via env flag and add basic assertions/snapshots.

  • Replace describe.skip with an ENV-gated helper so devs can opt-in locally/CI.
  • Assert non-empty items and snapshot a stable shape.
-describe.skip("/api/v1/users performance", () => {
+const RUN_PERF = process.env.RUN_PERF === "1";
+const describeIf = RUN_PERF ? describe : describe.skip;
+describeIf("/api/v1/users performance", () => {
@@
-    const response = await niceBackendFetch("/api/v1/users?limit=200000", { accessType: "server" });
+    const response = await niceBackendFetch("/api/v1/users?limit=200000", { accessType: "server" });
     const durationMs = performance.now() - start;
 
     expect(response.status).toBe(200);
+    expect(Array.isArray(response.body.items)).toBe(true);
+    expect(response.body.items.length).toBeGreaterThan(0);
+    expect(Object.keys(response.body)).toMatchInlineSnapshot([
+      "items",
+      "is_paginated",
+      "pagination",
+    ]);

As per coding guidelines.


25-48: Same gating and minimal snapshot for metrics.

  • Use ENV-gated describe.
  • Snapshot stable response keys; keep timings as logs.
-describe.skip("/api/v1/internal/metrics performance", () => {
+describeIf("/api/v1/internal/metrics performance", () => {
@@
-      const response = await niceBackendFetch("/api/v1/internal/metrics", { accessType: "admin" });
+      const response = await niceBackendFetch("/api/v1/internal/metrics", { accessType: "admin" });
       const durationMs = performance.now() - start;
       expect(response.status).toBe(200);
+      expect(Object.keys(response.body)).toMatchInlineSnapshot([
+        "total_users",
+        "daily_users",
+        "daily_active_users",
+        "users_by_country",
+        "recently_registered",
+        "recently_active",
+        "login_methods",
+      ]);
       return durationMs;
     };

As per coding guidelines.

apps/e2e/tests/backend/performance/mock-metric-events.sql (2)

50-67: Auth-method enrichment is solid; consider idempotency.

These blocks generate new AuthMethod/Passkey/OTP/OAuth rows on each run. If seeds may run multiple times, add ON CONFLICT DO NOTHING keyed on ("tenancyId","projectUserId", method kind) or a pre-clean step.

Also applies to: 88-117, 118-139, 140-168, 169-183


189-304: Event/IP data volume and cleanup strategy.

  • This creates ~300k Event and 300k EventIpInfo rows (300 users x 1000 events). Confirm this is the intended load for local/CI; consider parameterizing LIMIT and per-user event count via psql variables.
  • Since data includes seedTag ('perf-metrics-mock'), add an optional DELETE FROM "Event" WHERE data->>'seedTag'='perf-metrics-mock' prelude to keep runs bounded.

For long-term perf testing, keep a maintenance step to purge old seedRunId batches to control DB size.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c805a8 and 0122422.

📒 Files selected for processing (5)
  • apps/backend/src/app/api/latest/internal/metrics/route.tsx (7 hunks)
  • apps/backend/src/app/api/latest/users/crud.tsx (2 hunks)
  • apps/e2e/tests/backend/performance/metrics.test.ts (1 hunks)
  • apps/e2e/tests/backend/performance/mock-metric-events.sql (1 hunks)
  • apps/e2e/tests/backend/performance/mock-users.sql (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
apps/backend/src/app/api/latest/**

📄 CodeRabbit inference engine (AGENTS.md)

apps/backend/src/app/api/latest/**: Organize backend API routes by resource under /api/latest (e.g., auth at /api/latest/auth/, users at /api/latest/users/, teams at /api/latest/teams/, oauth providers at /api/latest/oauth-providers/)
Use the custom route handler system in the backend to ensure consistent API responses

Files:

  • apps/backend/src/app/api/latest/users/crud.tsx
  • apps/backend/src/app/api/latest/internal/metrics/route.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/backend/src/app/api/latest/users/crud.tsx
  • apps/backend/src/app/api/latest/internal/metrics/route.tsx
  • apps/e2e/tests/backend/performance/metrics.test.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values

Files:

  • apps/e2e/tests/backend/performance/metrics.test.ts
🧬 Code graph analysis (2)
apps/backend/src/app/api/latest/internal/metrics/route.tsx (3)
apps/backend/src/prisma-client.tsx (4)
  • getPrismaSchemaForTenancy (68-70)
  • globalPrismaClient (33-33)
  • sqlQuoteIdent (376-378)
  • getPrismaClientForTenancy (64-66)
apps/backend/src/lib/tenancies.tsx (1)
  • Tenancy (47-47)
apps/backend/src/app/api/latest/users/crud.tsx (2)
  • userFullInclude (28-47)
  • userPrismaToCrud (87-129)
apps/e2e/tests/backend/performance/metrics.test.ts (2)
apps/e2e/tests/backend/backend-helpers.ts (3)
  • backendContext (34-56)
  • InternalProjectKeys (75-80)
  • niceBackendFetch (107-171)
apps/e2e/tests/helpers.ts (1)
  • it (11-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: claude-review
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: Security Check
🔇 Additional comments (6)
apps/backend/src/app/api/latest/users/crud.tsx (1)

242-242: Refactor LGTM; reduces duplication.

Delegating to mapUserLastActiveAtMillis keeps the query/result mapping simple and consistent.

apps/e2e/tests/backend/performance/mock-users.sql (1)

1-4: Ensure pgcrypto availability in CI/dev.

CREATE EXTENSION may require elevated privileges. If your CI DB role lacks this, document a pre-step or guard via migration tooling.

apps/backend/src/app/api/latest/internal/metrics/route.tsx (3)

1-1: Imports and reuse from users CRUD: good integration.

Also applies to: 8-8


65-65: Type annotation tweak is fine.


137-137: Signature/spacing change: no issues.

apps/e2e/tests/backend/performance/mock-metric-events.sql (1)

14-31: Timestamp spreading logic LGTM. Distributes createdAt over last 30 days with hour/minute jitter.

Copy link

@recurseml recurseml bot left a comment

Choose a reason for hiding this comment

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

Review by RecurseML

🔍 Review performed on 463a7d2..0122422

✨ No bugs found, your code is sparkling clean

✅ Files analyzed, no issues (2)

apps/backend/src/app/api/latest/internal/metrics/route.tsx
apps/e2e/tests/backend/performance/metrics.test.ts

Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/backend/scripts/db-migrations.ts (2)

12-17: Dangerous in prod: DROP SCHEMA + GRANT ALL TO public

Add a hard environment guard before any destructive path, and avoid granting ALL to public outside dev/test. Gate these statements behind a prod check.

Example guard usage added below in main comment; also wrap GRANTs with a dev-only conditional.


176-191: Add a hard safety guard before destructive commands; wire --yes

Prevent accidental production drops and DB-target mismatch before running reset/generate-migration-file.

-  switch (command) {
+  switch (command) {
     case 'reset': {
-      await promptDropDb();
+      const force = await assertSafeToReset(args);
+      await promptDropDb(force);
       await dropSchema();
       await migrate();
       await seed();
       break;
     }
     case 'generate-migration-file': {
-      await promptDropDb();
+      const force = await assertSafeToReset(args);
+      await promptDropDb(force);
       await dropSchema();
       await migrate();
       await generateMigrationFile();
       await seed();
       break;
     }

Add this helper (place above generateMigrationFile or just before main):

+const isProductionLike = () => (process.env.NODE_ENV || '').toLowerCase().includes('prod');
+const parseDb = (raw: string) => {
+  try { const u = new URL(raw); return { host: u.host, db: u.pathname.replace(/^\//,'') }; }
+  catch { return null as any; }
+};
+async function assertSafeToReset(args: string[]) {
+  const force = args.includes('--yes') || process.env.FORCE_DB_RESET === '1';
+  const dbUrl = process.env.DATABASE_URL || '';
+  const directUrl = process.env.STACK_DIRECT_DATABASE_CONNECTION_STRING || dbUrl;
+  const a = parseDb(dbUrl), b = parseDb(directUrl);
+  const envUnsafe = isProductionLike();
+  const targetMismatch = !!(a && b && (a.host !== b.host || a.db !== b.db));
+  if ((envUnsafe || targetMismatch) && !force) {
+    throw new Error(`Refusing to run destructive command${envUnsafe ? ' in production' : ''}${targetMismatch ? ' with mismatched DB targets' : ''}. Re-run with --yes to override.`);
+  }
+  return force;
+}
♻️ Duplicate comments (3)
apps/backend/src/app/api/latest/internal/metrics/route.tsx (3)

49-50: Schema qualification still missing for Event and EventIpInfo.

As flagged in the previous review, these tables should be schema-qualified with ${sqlQuoteIdent(schema)}."Event" and ${sqlQuoteIdent(schema)}."EventIpInfo" for consistency with tenancy-scoped queries elsewhere in the codebase.


118-118: Schema qualification missing for Event table.

Similar to the issue in loadUsersByCountry, this query should use ${sqlQuoteIdent(schema)}."Event" for consistency with tenancy-scoped operations.


182-182: Schema qualification missing for Event table.

The Event table should be schema-qualified as ${sqlQuoteIdent(schema)}."Event" to match the pattern used in tenancy-scoped queries.

🧹 Nitpick comments (7)
apps/backend/src/lib/cache.tsx (1)

40-42: Cache stampede: concurrent requests can duplicate expensive work.

When an expired entry is found (or missing), multiple concurrent requests will all invoke loader() simultaneously before any result is cached. For expensive computations this defeats the purpose of caching.

Consider implementing a locking mechanism (e.g., advisory locks, in-memory promise cache, or optimistic "loading" sentinel) to ensure only one request computes the value while others wait. This is a common pattern in production cache libraries.

apps/backend/scripts/db-migrations.ts (6)

5-8: Optional: prefer node: specifiers for core modules

Use node:child_process, node:fs, node:path, node:readline for clarity and faster resolution.

-import { spawnSync } from "child_process";
-import fs from "fs";
-import path from "path";
-import * as readline from "readline";
+import { spawnSync } from "node:child_process";
+import fs from "node:fs";
+import path from "node:path";
+import * as readline from "node:readline";
```<!-- review_comment_end -->

---

`20-33`: **Non-interactive/CI-friendly prompt**

Support a `force`/`--yes` path and non‑TTY detection to avoid hanging in CI; also handle Ctrl‑C. 

```diff
-const askQuestion = (question: string) => {
+const askQuestion = (question: string) => {
   const rl = readline.createInterface({
     input: process.stdin,
     output: process.stdout,
   });
-  return new Promise<string>((resolve) => {
+  return new Promise<string>((resolve) => {
+    rl.on('SIGINT', () => { rl.close(); resolve(''); });
     rl.question(question, (answer) => {
       rl.close();
       resolve(answer);
     });
   });
 };
 
-const promptDropDb = async () => {
-  const answer = (await askQuestion(
+const promptDropDb = async (force = false) => {
+  if (force) return;
+  if (!process.stdout.isTTY) {
+    console.error('Non-interactive terminal. Re-run with --yes to skip confirmation.');
+    process.exit(1);
+  }
+  const answer = (await askQuestion(
     'Are you sure you want to drop everything in the database? This action cannot be undone. (y/N): ',
   )).trim();
   if (answer.toLowerCase() !== 'y') {
     console.log('Operation cancelled');
     process.exit(0);
   }
 };
```<!-- review_comment_end -->

---

`80-97`: **CLI portability**

Relying on `pnpm prisma` requires pnpm on PATH. If this script is used outside pnpm contexts, consider `npx prisma` or invoking the local binary via package.json script. 
<!-- review_comment_end -->

---

`118-121`: **Migration summary counts/labels when selecting specific migrations**

When `selectedMigrationFiles` is used (e.g., applying the just-generated one), “Total migrations in folder” becomes misleading. Compute folder total separately and label considered vs folder totals. 

```diff
-  const migrationFiles = selectedMigrationFiles ?? getMigrationFiles(MIGRATION_FILES_DIR);
-  const totalMigrations = migrationFiles.length;
+  const migrationFiles = selectedMigrationFiles ?? getMigrationFiles(MIGRATION_FILES_DIR);
+  const folderTotal = getMigrationFiles(MIGRATION_FILES_DIR).length;
+  const consideredTotal = migrationFiles.length;
@@
-  console.log(`📁 Total migrations in folder: ${totalMigrations}`);
-  console.log(`🆕 Newly applied migrations: ${result.newlyAppliedMigrationNames.length}`);
-  console.log(`✓  Already applied migrations: ${totalMigrations - result.newlyAppliedMigrationNames.length}`);
+  console.log(`📁 Migrations in folder: ${folderTotal}`);
+  if (selectedMigrationFiles) console.log(`🎯 Migrations considered: ${consideredTotal}`);
+  console.log(`🆕 Newly applied migrations: ${result.newlyAppliedMigrationNames.length}`);
+  console.log(`✓  Already applied migrations: ${consideredTotal - result.newlyAppliedMigrationNames.length}`);
```<!-- review_comment_end -->



Also applies to: 133-141

---

`162-169`: **Help text drift**

Reflect current behavior and add `--yes` flag. 

```diff
 Commands:
-  reset                    Drop all data and recreate the database, then apply migrations and seed
-  generate-migration-file  Generate a new migration file using Prisma, then reset and migrate
+  reset                    Drop all data and recreate the database, then apply migrations and seed
+  generate-migration-file  Generate and apply a new migration file from schema changes, then seed
   seed                     [Advanced] Run database seeding only
   init                     Apply migrations and seed the database
   migrate                  Apply migrations
   help                     Show this help message
+
+Flags:
+  --yes                    Skip confirmation prompts for destructive operations
```<!-- review_comment_end -->

---

`10-10`: **Avoid deep import to /dist if possible**

Prefer a stable public entrypoint (e.g., `@stackframe/stack-shared/utils/env`) to reduce coupling to build output. 


If the package exposes a public export, switch to it; otherwise ignore.<!-- review_comment_end -->

<!-- file_end -->

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 01224227ee924a6d76ad3f624375bd8fc3ee2e2c and 4d9be71063ee33be7e11bbeb5ab1256f3f86fe71.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `.github/workflows/check-prisma-migrations.yaml` (0 hunks)
* `apps/backend/prisma/migrations/20251020210214_add_cache_entry/migration.sql` (1 hunks)
* `apps/backend/prisma/schema.prisma` (2 hunks)
* `apps/backend/scripts/db-migrations.ts` (3 hunks)
* `apps/backend/src/app/api/latest/internal/metrics/route.tsx` (7 hunks)
* `apps/backend/src/lib/cache.tsx` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* .github/workflows/check-prisma-migrations.yaml

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (2)</summary>

<details>
<summary>**/*.{ts,tsx,js,jsx}</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> Prefer ES6 Map over Record when representing key–value collections

Files:
- `apps/backend/scripts/db-migrations.ts`
- `apps/backend/src/lib/cache.tsx`
- `apps/backend/src/app/api/latest/internal/metrics/route.tsx`

</details>
<details>
<summary>apps/backend/src/app/api/latest/**</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> `apps/backend/src/app/api/latest/**`: Organize backend API routes by resource under /api/latest (e.g., auth at /api/latest/auth/*, users at /api/latest/users/*, teams at /api/latest/teams/*, oauth providers at /api/latest/oauth-providers/*)
> Use the custom route handler system in the backend to ensure consistent API responses

Files:
- `apps/backend/src/app/api/latest/internal/metrics/route.tsx`

</details>

</details><details>
<summary>🧬 Code graph analysis (3)</summary>

<details>
<summary>apps/backend/scripts/db-migrations.ts (2)</summary><blockquote>

<details>
<summary>apps/backend/src/auto-migrations/utils.tsx (2)</summary>

* `MIGRATION_FILES_DIR` (5-5)
* `getMigrationFiles` (7-30)

</details>
<details>
<summary>packages/stack-shared/src/utils/env.tsx (1)</summary>

* `getEnvVariable` (16-58)

</details>

</blockquote></details>
<details>
<summary>apps/backend/src/lib/cache.tsx (1)</summary><blockquote>

<details>
<summary>apps/backend/src/prisma-client.tsx (1)</summary>

* `PrismaClientTransaction` (18-18)

</details>

</blockquote></details>
<details>
<summary>apps/backend/src/app/api/latest/internal/metrics/route.tsx (4)</summary><blockquote>

<details>
<summary>apps/backend/src/lib/tenancies.tsx (1)</summary>

* `Tenancy` (47-47)

</details>
<details>
<summary>apps/backend/src/prisma-client.tsx (5)</summary>

* `PrismaClientTransaction` (18-18)
* `getPrismaSchemaForTenancy` (68-70)
* `globalPrismaClient` (33-33)
* `sqlQuoteIdent` (376-378)
* `getPrismaClientForTenancy` (64-66)

</details>
<details>
<summary>apps/backend/src/lib/cache.tsx (1)</summary>

* `getOrSetCacheValue` (19-64)

</details>
<details>
<summary>apps/backend/src/app/api/latest/users/crud.tsx (2)</summary>

* `userFullInclude` (28-47)
* `userPrismaToCrud` (87-129)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)</summary>

* GitHub Check: check_prisma_migrations (22.x)
* GitHub Check: docker
* GitHub Check: lint_and_build (latest)
* GitHub Check: setup-tests
* GitHub Check: all-good
* GitHub Check: build (22.x)
* GitHub Check: build (22.x)
* GitHub Check: docker
* GitHub Check: restart-dev-and-test
* GitHub Check: Security Check

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>apps/backend/prisma/migrations/20251020210214_add_cache_entry/migration.sql (1)</summary><blockquote>

`1-15`: **Migration looks good.**

The CacheEntry table structure correctly implements the caching requirements with appropriate constraints and indexes.

</blockquote></details>
<details>
<summary>apps/backend/src/app/api/latest/internal/metrics/route.tsx (3)</summary><blockquote>

`14-25`: **Well-structured caching integration.**

The cache key composition correctly isolates metrics by tenancy and anonymous-user inclusion, and the 1-hour TTL is appropriate for analytics use cases.

---

`205-220`: **Excellent refactor to batch user loading.**

Replacing per-user `adminRead` calls with a single `findMany` query significantly improves performance. The reuse of `userFullInclude` and `userPrismaToCrud` from the CRUD module promotes consistency.

---

`269-301`: **Strategic caching of expensive metric queries.**

The selective caching of `daily_active_users`, `users_by_country`, and `recently_active_users` appropriately targets the most computationally expensive metrics while leaving simpler queries uncached.

</blockquote></details>
<details>
<summary>apps/backend/prisma/schema.prisma (1)</summary><blockquote>

`842-853`: **No duplicates found—the review comment is based on incorrect AI analysis.**

The verification confirms only one `CacheEntry` model exists at line 843. The AI summary incorrectly claimed duplicate definitions. The schema is correct; no action required.



> Likely an incorrect or invalid review comment.

</blockquote></details>
<details>
<summary>apps/backend/scripts/db-migrations.ts (4)</summary><blockquote>

`45-51`: **Migration name sanitizer looks good** 
<!-- review_comment_end -->

---

`52-69`: **Interactive migration name UX is solid** 
<!-- review_comment_end -->

---

`70-70`: **Timestamp prefix OK** 
<!-- review_comment_end -->

---

`72-116`: **The review comment about DATABASE_URL fallback is incorrect and does not apply to this codebase.**

The review assumes `DATABASE_URL` is a competing connection string variable, but verification confirms:
- `DATABASE_URL` does not exist in the backend codebase (zero occurrences in TypeScript files and .env configuration)
- `STACK_DIRECT_DATABASE_CONNECTION_STRING` is the sole database connection variable used
- No DB mismatch scenario exists, as there is no second connection target

The `getEnvVariable()` function does support an optional `defaultValue` parameter, but proposing `DATABASE_URL` as a fallback is inapplicable when that variable is not configured anywhere in the backend environment.



> Likely an incorrect or invalid review comment.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link
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

♻️ Duplicate comments (3)
apps/backend/src/app/api/latest/internal/metrics/route.tsx (3)

108-151: Schema-qualify Event in DAU query. Add indexes (ops).

Qualify Event; suggest partial indexes for JSON filters to keep performant at scale.

Apply:

+  const schema = await getPrismaSchemaForTenancy(tenancy);
-  const res = await globalPrismaClient.$queryRaw<{ day: Date, dau: bigint }[]>`
+  const res = await globalPrismaClient.$queryRaw<{ day: Date, dau: bigint }[]>`
 ...
-      FROM "Event"
+      FROM ${sqlQuoteIdent(schema)}."Event"
 ...
   `;

Operational (migration example):

  • Index on project/branch and event type:
    CREATE INDEX CONCURRENTLY IF NOT EXISTS evt_proj_branch_type_started_at
    ON ""."Event" ((data->>'projectId'), COALESCE((data->>'branchId'),'main'), "eventStartedAt")
    WHERE '$user-activity' = ANY("systemEventTypeIds"::text[]);

  • Optional: expression index on (data->>'userId').


50-71: Schema-qualify Event/EventIpInfo in users-by-country.

Unqualified tables risk hitting the wrong schema. Qualify with tenancy schema. This mirrors other routes.

Apply:

+  const schema = await getPrismaSchemaForTenancy(tenancy);
   const rows = await globalPrismaClient.$queryRaw<{ countryCode: string | null, userCount: bigint }[]>(Prisma.sql`
     WITH latest_ip AS (
       SELECT DISTINCT ON (e."data"->>'userId')
         e."data"->>'userId' AS "userId",
         eip."countryCode" AS "countryCode"
-      FROM "Event" e
-      JOIN "EventIpInfo" eip
+      FROM ${sqlQuoteIdent(schema)}."Event" e
+      JOIN ${sqlQuoteIdent(schema)}."EventIpInfo" eip
         ON eip.id = e."endUserIpInfoGuessId"
       WHERE '$user-activity' = ANY(e."systemEventTypeIds"::text[])
         AND e."data"->>'projectId' = ${tenancy.project.id}
         AND COALESCE(e."data"->>'branchId', 'main') = ${tenancy.branchId}
         AND e."data"->>'userId' = ANY(${userIdArray})
         AND e."endUserIpInfoGuessId" IS NOT NULL
         AND eip."countryCode" IS NOT NULL
       ORDER BY e."data"->>'userId', e."eventStartedAt" DESC
     )
     SELECT "countryCode", COUNT("userId") AS "userCount"
     FROM latest_ip
     GROUP BY "countryCode"
     ORDER BY "userCount" DESC;
   `);

179-205: Schema-qualify Event in recently-active query.

Same consistency issue; qualify table with tenancy schema.

Apply:

+  const schema = await getPrismaSchemaForTenancy(tenancy);
   const events = await globalPrismaClient.$queryRaw<{ userId: string, lastActiveAt: Date }[]>`
 ...
-      FROM "Event"
+      FROM ${sqlQuoteIdent(schema)}."Event"
 ...
   `;
🧹 Nitpick comments (2)
apps/backend/src/app/api/latest/internal/metrics/route.tsx (2)

15-17: Make TTL configurable (optional).

Consider reading TTL from env/config to tune per environment.


191-194: LIMIT 4000 may drop true top-N distinct users under bursty single-user traffic.

Either (a) remove this LIMIT and add a time window (e.g., last 7 days), or (b) increase progressively until N distinct users found.

Example change:

-      ORDER BY "eventStartedAt" DESC
-      LIMIT 4000
+      WHERE "eventStartedAt" >= ${new Date(Date.now() - 7*24*60*60*1000)}::timestamptz
+      ORDER BY "eventStartedAt" DESC
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d9be71 and 45ca5b3.

📒 Files selected for processing (2)
  • .github/workflows/check-prisma-migrations.yaml (0 hunks)
  • apps/backend/src/app/api/latest/internal/metrics/route.tsx (6 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/check-prisma-migrations.yaml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use ES6 Maps instead of Records wherever possible in TypeScript code

Files:

  • apps/backend/src/app/api/latest/internal/metrics/route.tsx
🧬 Code graph analysis (1)
apps/backend/src/app/api/latest/internal/metrics/route.tsx (4)
apps/backend/src/lib/tenancies.tsx (1)
  • Tenancy (47-47)
apps/backend/src/prisma-client.tsx (4)
  • PrismaClientTransaction (22-22)
  • globalPrismaClient (37-37)
  • getPrismaSchemaForTenancy (72-74)
  • getPrismaClientForTenancy (68-70)
apps/backend/src/lib/cache.tsx (1)
  • getOrSetCacheValue (19-64)
apps/backend/src/app/api/latest/users/crud.tsx (2)
  • userFullInclude (28-47)
  • userPrismaToCrud (87-129)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: docker
  • GitHub Check: Security Check
🔇 Additional comments (7)
apps/backend/src/app/api/latest/internal/metrics/route.tsx (7)

2-3: Imports: looks good.

Nothing to fix here.

Also applies to: 9-11


34-47: Prefiltering users is OK.

Fetching candidate userIds up front to honor includeAnonymous is reasonable.


81-105: Total users query: LGTM.

Proper schema-qualification and windowed CTE look correct.


153-177: Login methods: LGTM.

Already tenancy-qualified; query shape is fine.


221-226: Mapping to CRUD objects: LGTM.

Small set, in-memory find is fine here.


274-287: Cache wrapping: LGTM.

Good use of includeAnonymous in cache key and tenancy scoping.


18-26: Clarify cache key intent and verify client usage for global CacheEntry.

The cache key concern is overstated: tenancy.id is already a globally unique UUID, preventing cross-branch collisions. However, your suggestion to add projectId and branchId is reasonable defensive coding for clarity—though not strictly necessary for correctness.

More importantly, verify whether a tenant-scoped Prisma client (returned by getPrismaClientForTenancy) should be querying the global CacheEntry table. While the schema is shared across clients, this warrants confirmation to ensure consistency.

Consider either:

  1. If tenant client is correct: Remove the collision concern; the current key is already safe.
  2. If globalPrismaClient should be used: Pass globalPrismaClient explicitly to getOrSetCacheValue instead of the tenant-scoped client.

If you opt to add projectId and branchId for defensive clarity, apply the diff; otherwise, no changes needed.

@BilalG1 BilalG1 merged commit b5b3115 into dev Nov 6, 2025
19 checks passed
@BilalG1 BilalG1 deleted the perf/user-list branch November 6, 2025 00:24
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