Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: analytics api #2740

Merged
merged 37 commits into from
Jan 10, 2025
Merged

feat: analytics api #2740

merged 37 commits into from
Jan 10, 2025

Conversation

chronark
Copy link
Collaborator

@chronark chronark commented Dec 11, 2024

  • chore: replace button references
  • wip

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new analytics API for tracking and analyzing verification events.
    • Introduced a public getter method to access the internal querier for quick iterations.
    • Added a new property for ClickHouse database connection in the configuration.
    • Enhanced the Harness class with ClickHouse support.
    • Introduced optional query parameters for the analytics endpoint.
    • Added new caching capabilities for identity by ID.
    • Created a new migration step for ClickHouse in the workflow.
  • Documentation

    • Created detailed overview and API documentation for analytics.
    • Updated documentation navigation to include a new analytics section.
  • Improvements

    • Enhanced error handling in various API routes.
    • Improved query parameter flexibility for the analytics endpoint.
    • Expanded functionality of the StepRequest type with optional search parameters.
    • Updated response schema for the analytics endpoint to include additional fields.
  • Chores

    • Consolidated package configuration files.
    • Streamlined test scripts in package configurations.

Copy link

vercel bot commented Dec 11, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
engineering ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 10, 2025 0:41am
play ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 10, 2025 0:41am
www ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 10, 2025 0:41am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
dashboard ⬜️ Ignored (Inspect) Visit Preview Jan 10, 2025 0:41am

Copy link

changeset-bot bot commented Dec 11, 2024

⚠️ No Changeset found

Latest commit: 9d05f32

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Dec 11, 2024

📝 Walkthrough

Walkthrough

This pull request introduces multiple enhancements to the Unkey analytics system across various files. Key changes include the addition of a public getter method internalQuerier in the Analytics class, a new CLICKHOUSE_URL property for database configuration, and enhancements to the Harness class for ClickHouse support. The analytics verification route has been updated with new optional query parameters and validation logic, alongside improved error handling. Additionally, new documentation files and a structured cache system for identities have been implemented, facilitating better data retrieval and analytics capabilities.

Changes

File Change Summary
apps/api/src/pkg/analytics.ts Added internalQuerier public getter method to Analytics class
apps/api/src/pkg/testutil/env.ts Added CLICKHOUSE_URL property to databaseEnv object schema
apps/api/src/pkg/testutil/harness.ts Added ClickHouse support, updated Harness class with new ch property, and modified createKey method return type
apps/api/src/pkg/testutil/request.ts Enhanced StepRequest type with optional searchparams and improved URL handling
apps/api/src/routes/v1_analytics_getVerifications.ts Extensive modifications to analytics verification route, adding new query parameters, validation, and enhanced response structure
apps/api/src/worker.ts Registered new registerV1AnalyticsGetVerifications route
apps/docs/analytics/api.mdx Created new documentation file for API
apps/docs/analytics/overview.mdx Created new documentation file for overview of analytics API
apps/docs/mint.json Added new "Analytics" group to documentation navigation
apps/api/src/pkg/cache/index.ts Added identityById namespace to cache initialization
apps/api/src/pkg/cache/namespaces.ts Added identityById property to CacheNamespaces type
apps/docs/mint.json Added new group for "Analytics" with pages: ["analytics/overview"]

Possibly related PRs

Suggested labels

Feature, 🕹️ oss.gg, :joystick: 300 points, hacktoberfest

Suggested reviewers

  • mcstepp
  • perkinsjr
  • MichaelUnkey
  • ogzhanolguncu

Finishing Touches

  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

github-actions bot commented Dec 11, 2024

Thank you for following the naming conventions for pull request titles! 🙏

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 (4)
apps/api/src/routes/v1_analytics_getVerifications.ts (2)

100-118: Consider using a const enum for orderBy values.

The orderBy validation could be more type-safe by using a const enum or as const array.

Apply this diff to improve type safety:

+const ORDER_BY_VALUES = [
+  "time",
+  "valid",
+  "notFound",
+  "forbidden",
+  "usageExceeded",
+  "rateLimited",
+  "unauthorized",
+  "disabled",
+  "insufficientPermissions",
+  "expired",
+  "total",
+] as const;

-        .enum([
-          "time",
-          "valid",
-          "notFound",
-          "forbidden",
-          "usageExceeded",
-          "rateLimited",
-          "unauthorized",
-          "disabled",
-          "insufficientPermissions",
-          "expired",
-          "total",
-        ])
+        .enum(ORDER_BY_VALUES)

465-481: Optimize identity fetching with batched queries.

The current implementation makes individual cache/DB queries for each identity. Consider batching these queries for better performance.

Consider this approach:

const identityIds = Object.keys(identityIds);
// Batch fetch from cache first
const cacheResults = await Promise.all(
  chunk(identityIds, 100).map(async (ids) => {
    const cacheKeys = ids.map(id => ({ key: id, type: "identityById" as const }));
    return cache.batchGet(cacheKeys);
  })
);

// Batch fetch missing ones from DB
const missingIds = identityIds.filter(id => !cacheResults.flat().find(r => r.key === id));
if (missingIds.length > 0) {
  const dbResults = await db.readonly.query.identities.findMany({
    where: (table, { and, eq, inArray }) =>
      and(
        eq(table.workspaceId, auth.authorizedWorkspaceId),
        inArray(table.id, missingIds)
      ),
  });
  // Update cache with new results
  await Promise.all(
    dbResults.map(identity => 
      cache.identityById.set(identity.id, identity)
    )
  );
}
apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (2)

9-9: Define outcome constants in a separate constants file.

The POSSIBLE_OUTCOMES constant could be shared with the main implementation file.

Consider moving this to a shared constants file:

// src/constants/analytics.ts
export const VERIFICATION_OUTCOMES = ["VALID", "RATE_LIMITED", "DISABLED"] as const;
export type VerificationOutcome = typeof VERIFICATION_OUTCOMES[number];

1189-1197: Enhance test data generation with more configuration options.

The generate function could be more flexible to support different test scenarios.

Consider adding these options:

type GenerateOptions = {
  start: number;
  end: number;
  length: number;
  workspaceId: string;
  keySpaceId: string;
  keys: Array<{ keyId: string; identityId?: string }>;
  tags?: string[];
  // Add these options:
  outcomeDistribution?: Partial<Record<VerificationOutcome, number>>;
  timeDistribution?: "random" | "uniform" | "weighted";
  regionList?: string[];
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2fb8e5 and 12cc2aa.

📒 Files selected for processing (4)
  • apps/api/src/pkg/cache/index.ts (1 hunks)
  • apps/api/src/pkg/cache/namespaces.ts (1 hunks)
  • apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (1 hunks)
  • apps/api/src/routes/v1_analytics_getVerifications.ts (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (17)
  • GitHub Check: Test Packages / Test ./packages/rbac
  • GitHub Check: Test Packages / Test ./packages/nextjs
  • GitHub Check: Test Packages / Test ./packages/hono
  • GitHub Check: Test Packages / Test ./packages/cache
  • GitHub Check: Test Packages / Test ./packages/api
  • GitHub Check: Test Packages / Test ./internal/clickhouse
  • GitHub Check: Test Packages / Test ./internal/resend
  • GitHub Check: Test Packages / Test ./internal/keys
  • GitHub Check: Test Packages / Test ./internal/id
  • GitHub Check: Test Packages / Test ./internal/hash
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test ./internal/encryption
  • GitHub Check: Test Packages / Test ./internal/billing
  • GitHub Check: Build / Build
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: autofix
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
apps/api/src/pkg/cache/namespaces.ts (1)

66-66: LGTM! The cache namespace type is well-defined.

The addition of identityById follows the established pattern and properly handles cache misses with the nullable type.

apps/api/src/pkg/cache/index.ts (1)

74-74: LGTM! Cache namespace initialization is consistent.

The identityById namespace is properly initialized with the same configuration as other namespaces, maintaining consistency in the caching layer.

apps/api/src/routes/v1_analytics_getVerifications.ts (3)

447-456: ⚠️ Potential issue

Improve error handling and logging security.

The error handling exposes internal details in both logs and error messages.

Apply this diff to improve security:

     if (data.err) {
       logger.error("unable to query clickhouse", {
-        error: data.err.message,
-        query: query,
+        error: data.err.message.replace(/'[^']*'/g, "'***'"), // Redact values
+        queryType: "analytics_verification",
       });
       throw new UnkeyApiError({
         code: "INTERNAL_SERVER_ERROR",
-        message: `unable to query clickhouse: ${data.err.message}`,
+        message: "An error occurred while processing analytics data",
       });
     }

Likely invalid or redundant comment.


208-212: 🛠️ Refactor suggestion

Avoid exposing internal details in error messages.

The error message includes the raw filters object which could expose sensitive information.

Apply this diff to provide a cleaner error message:

-        message: `You can not group by tag and tags at the same time, received: ${JSON.stringify(
-          filters,
-        )}`,
+        message: "Cannot group by both 'tag' and 'tags' simultaneously",

Likely invalid or redundant comment.


397-397: ⚠️ Potential issue

Fix potential SQL injection in ORDER BY clause.

Using { orderBy: Identifier } is unsafe as it allows arbitrary column names.

Apply this diff to use a safe mapping:

-      query.push(`ORDER BY { orderBy: Identifier } ${filters.order === "asc" ? "ASC" : "DESC"} `);
+      const safeOrderByMap: Record<typeof ORDER_BY_VALUES[number], string> = {
+        time: "time",
+        valid: "valid",
+        notFound: "notFound",
+        forbidden: "forbidden",
+        usageExceeded: "usageExceeded",
+        rateLimited: "rateLimited",
+        unauthorized: "unauthorized",
+        disabled: "disabled",
+        insufficientPermissions: "insufficientPermissions",
+        expired: "expired",
+        total: "total",
+      };
+      query.push(`ORDER BY ${safeOrderByMap[filters.orderBy]} ${filters.order === "asc" ? "ASC" : "DESC"} `);

Likely invalid or redundant comment.

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 (5)
.github/workflows/job_test_api_local.yaml (1)

38-44: Consider security and robustness improvements for ClickHouse migration

While the migration step is functionally correct, consider these improvements:

  1. Avoid hardcoding credentials in the workflow file
  2. Add error handling and migration status verification
  3. Ensure the migration directory path is resilient to repository structure changes

Example improvement:

      - name: Migrate ClickHouse
        run: |
+         if ! goose status; then
+           echo "Failed to check migration status"
+           exit 1
+         fi
          goose up
+         if [ $? -ne 0 ]; then
+           echo "Migration failed"
+           exit 1
+         fi
        env:
          GOOSE_DRIVER: clickhouse
-         GOOSE_DBSTRING: "tcp://default:[email protected]:9000"
+         GOOSE_DBSTRING: ${{ secrets.CLICKHOUSE_URL }}
          GOOSE_MIGRATION_DIR: ./internal/clickhouse/schema
apps/api/src/routes/v1_analytics_getVerifications.ts (2)

82-89: Consider adding validation for mutually exclusive groupBy values.

The description mentions that grouping by tags and tag is mutually exclusive, but this constraint isn't enforced at the schema level. While the code handles this later with a runtime check, consider adding this validation in the schema for better API documentation and early validation.

-      groupBy: validation.groupBy
-        .or(z.array(validation.groupBy))
-        .optional()
+      groupBy: z
+        .union([
+          validation.groupBy,
+          z.array(validation.groupBy.refine(
+            (arr) => !(arr.includes("tag") && arr.includes("tags")),
+            "Cannot group by both 'tag' and 'tags'"
+          ))
+        ])
+        .optional()

319-352: Consider implementing retry logic for cache operations.

The API lookup cache operation could benefit from retry logic to handle transient failures. Also, consider implementing a circuit breaker pattern for the database queries.

apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (2)

9-10: Consider using TypeScript enum for outcomes.

Using a const array for possible outcomes could be improved by using a TypeScript enum for better type safety and maintainability.

-const POSSIBLE_OUTCOMES = ["VALID", "RATE_LIMITED", "DISABLED"] as const;
+enum VerificationOutcome {
+  VALID = "VALID",
+  RATE_LIMITED = "RATE_LIMITED",
+  DISABLED = "DISABLED",
+}

1227-1229: Consider adding input validation to toStartOfHour.

The toStartOfHour function should validate its input to ensure it's a valid timestamp.

 function toStartOfHour(unixmilli: number): number {
+  if (!Number.isFinite(unixmilli) || unixmilli < 0) {
+    throw new Error('Invalid timestamp provided');
+  }
   return Math.floor(unixmilli / 60 / 60 / 1000) * 60 * 60 * 1000;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 631e884 and acdd6f3.

📒 Files selected for processing (3)
  • .github/workflows/job_test_api_local.yaml (2 hunks)
  • apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (1 hunks)
  • apps/api/src/routes/v1_analytics_getVerifications.ts (5 hunks)
🧰 Additional context used
📓 Learnings (1)
apps/api/src/routes/v1_analytics_getVerifications.ts (2)
Learnt from: chronark
PR: unkeyed/unkey#2740
File: apps/api/src/routes/v1_analytics_getVerifications.ts:259-259
Timestamp: 2025-01-10T08:31:33.471Z
Learning: In the Unkey codebase, workspace_id values obtained from auth.authorizedWorkspaceId are safe to use in SQL queries without parameterization since they are internally controlled and validated by the authentication system, not user-provided.
Learnt from: chronark
PR: unkeyed/unkey#2740
File: apps/api/src/routes/v1_analytics_getVerifications.ts:399-402
Timestamp: 2025-01-10T08:35:51.282Z
Learning: Route-level Zod validation for query parameters (e.g., orderBy) is sufficient for preventing SQL injection, as the validation happens before the handler runs, ensuring only valid values reach the query construction.
⏰ Context from checks skipped due to timeout of 90000ms (14)
  • GitHub Check: Test Packages / Test ./packages/nextjs
  • GitHub Check: Test Packages / Test ./packages/hono
  • GitHub Check: Test Packages / Test ./packages/cache
  • GitHub Check: Test Packages / Test ./packages/api
  • GitHub Check: Test Packages / Test ./internal/clickhouse
  • GitHub Check: Test Packages / Test ./internal/resend
  • GitHub Check: Test Packages / Test ./internal/hash
  • GitHub Check: Test Packages / Test ./internal/encryption
  • GitHub Check: Test Packages / Test ./internal/billing
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Build / Build
  • GitHub Check: autofix
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
.github/workflows/job_test_api_local.yaml (1)

26-26: LGTM: Go installation added for ClickHouse support

The addition of Go installation support is necessary for the new ClickHouse migration tooling.

apps/api/src/routes/v1_analytics_getVerifications.ts (3)

9-11: LGTM! Well-structured validation setup.

The validation object clearly defines the allowed groupBy values, making it easy to maintain and extend.


201-211: LGTM! Proper validation of mutually exclusive groupBy values.

The runtime check for mutually exclusive groupBy values is implemented correctly with a clear error message.


449-458: ⚠️ Potential issue

Improve error logging for database queries.

The error logging includes the full SQL query which might expose sensitive information. Consider logging only the error message and a query identifier for debugging.

-      logger.error("unable to query clickhouse", {
-        error: data.err.message,
-        query: query,
-      });
+      logger.error("unable to query clickhouse", {
+        error: data.err.message,
+        queryId: crypto.randomUUID(),
+      });

Likely invalid or redundant comment.

apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (1)

1198-1222: LGTM! Well-documented and type-safe test data generation.

The generate function is well-documented with TypeScript types and provides good flexibility for test data generation.

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: 0

🧹 Nitpick comments (5)
apps/api/src/routes/v1_analytics_getVerifications.ts (2)

220-242: Consider improving query construction.

While the query construction is functional, consider these improvements:

  1. Define a type for the table configuration to ensure type safety.
  2. Simplify the table selection logic by using an object lookup instead of if-else chains.

Apply this diff to improve type safety and readability:

 const tables = {
   hour: {
     name: "verifications.key_verifications_per_hour_v3",
     fill: `WITH FILL
FROM toStartOfHour(fromUnixTimestamp64Milli({ start: Int64 }))
TO toStartOfHour(fromUnixTimestamp64Milli({ end: Int64 }))
STEP INTERVAL 1 HOUR`,
   },
   day: {
     name: "verifications.key_verifications_per_day_v3",
     fill: `WITH FILL
FROM toStartOfDay(fromUnixTimestamp64Milli({ start: Int64 }))
TO toStartOfDay(fromUnixTimestamp64Milli({ end: Int64 }))
STEP INTERVAL 1 DAY`,
   },
   month: {
     name: "verifications.key_verifications_per_month_v3",
     fill: `WITH FILL
FROM toDateTime(toStartOfMonth(fromUnixTimestamp64Milli({ start: Int64 })))
TO toDateTime(toStartOfMonth(fromUnixTimestamp64Milli({ end: Int64 })))
STEP INTERVAL 1 MONTH`,
   },
-} as const;
+} satisfies Record<string, {
+  name: string;
+  fill: string;
+}>;

+type TableGranularity = keyof typeof tables;
+const getTable = (granularity: TableGranularity) => tables[granularity];

-let table: ValueOf<typeof tables> = tables.hour;
+const table = getTable(selectedGroupBy.find((g): g is TableGranularity => g in tables) ?? "hour");

Also applies to: 387-402


332-348: Improve error message clarity and security.

The error messages could be more user-friendly while maintaining security:

Apply this diff to improve the error messages:

-        message: "we're unable to load the API",
+        message: "Failed to retrieve API configuration",

-        message: "we're unable to find the API",
+        message: "API not found",

-        message: "api has no keyspace attached",
+        message: "API is not properly configured for key management",
apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (3)

11-38: Enhance test descriptions and type safety.

While the test coverage is comprehensive, consider these improvements:

  1. Make test descriptions more specific about what they're validating.
  2. Add type safety to the test data generation.

Apply this diff to improve the test descriptions:

-describe("with no data", () => {
+describe("getVerifications with no data", () => {
-  test("returns an array with one element per interval", async (t) => {
+  test("should return an array with one element per hour when no verifications exist", async (t) => {

-  test("a user's usage over the past 24h for 2 keys", async (t) => {
+  test("should aggregate usage statistics for multiple keys over 24 hours", async (t) => {

-  test("A monthly cron job creates invoices for each identity", async (t) => {
+  test("should provide monthly usage statistics for billing purposes", async (t) => {

Also applies to: 126-127, 361-362


1189-1223: Improve test data generation utility.

The test data generation utility could benefit from these improvements:

  1. Use a seeded random number generator for deterministic tests.
  2. Add more precise type definitions.

Apply this diff to improve the utility:

+/**
+ * A deterministic random number generator for test stability
+ */
+function seededRandom(seed: number) {
+  return () => {
+    seed = (seed * 16807) % 2147483647;
+    return (seed - 1) / 2147483646;
+  };
+}

+type VerificationOutcome = typeof POSSIBLE_OUTCOMES[number];
+interface KeyConfig {
+  keyId: string;
+  identityId?: string;
+}

 function generate(opts: {
   start: number;
   end: number;
   length: number;
   workspaceId: string;
   keySpaceId: string;
-  keys: Array<{ keyId: string; identityId?: string }>;
+  keys: Array<KeyConfig>;
   tags?: string[];
 }) {
+  const random = seededRandom(opts.start);
   return Array.from({ length: opts.length }).map((_) => {
-    const key = opts.keys[Math.floor(Math.random() * opts.keys.length)];
+    const key = opts.keys[Math.floor(random() * opts.keys.length)];

     return {
-      time: Math.round(Math.random() * (opts.end - opts.start) + opts.start),
+      time: Math.round(random() * (opts.end - opts.start) + opts.start),
       workspace_id: opts.workspaceId,
       key_space_id: opts.keySpaceId,
       key_id: key.keyId,
-      outcome: POSSIBLE_OUTCOMES[Math.floor(Math.random() * POSSIBLE_OUTCOMES.length)],
+      outcome: POSSIBLE_OUTCOMES[Math.floor(random() * POSSIBLE_OUTCOMES.length)],
       tags: opts.tags ?? [],
       request_id: newId("test"),
       region: "test",
       identity_id: key.identityId,
     };
   });
 }

1225-1230: Enhance timestamp utility function.

The timestamp utility function could be improved with better validation and precision:

Apply this diff to improve the utility:

 /**
  * Truncates the timestamp to the start of the current hour
+ * @throws {Error} If the timestamp is invalid
  */
 function toStartOfHour(unixmilli: number): number {
+  if (!Number.isFinite(unixmilli) || unixmilli < 0) {
+    throw new Error('Invalid timestamp');
+  }
-  return Math.floor(unixmilli / 60 / 60 / 1000) * 60 * 60 * 1000;
+  return Math.floor(unixmilli / (60 * 60 * 1000)) * (60 * 60 * 1000);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acdd6f3 and 9d05f32.

📒 Files selected for processing (2)
  • apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (1 hunks)
  • apps/api/src/routes/v1_analytics_getVerifications.ts (5 hunks)
🧰 Additional context used
📓 Learnings (1)
apps/api/src/routes/v1_analytics_getVerifications.ts (2)
Learnt from: chronark
PR: unkeyed/unkey#2740
File: apps/api/src/routes/v1_analytics_getVerifications.ts:259-259
Timestamp: 2025-01-10T08:31:33.471Z
Learning: In the Unkey codebase, workspace_id values obtained from auth.authorizedWorkspaceId are safe to use in SQL queries without parameterization since they are internally controlled and validated by the authentication system, not user-provided.
Learnt from: chronark
PR: unkeyed/unkey#2740
File: apps/api/src/routes/v1_analytics_getVerifications.ts:399-402
Timestamp: 2025-01-10T08:35:51.282Z
Learning: Route-level Zod validation for query parameters (e.g., orderBy) is sufficient for preventing SQL injection, as the validation happens before the handler runs, ensuring only valid values reach the query construction.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Test Agent Local / test_agent_local
🔇 Additional comments (2)
apps/api/src/routes/v1_analytics_getVerifications.ts (2)

9-11: LGTM! The validation logic is well-structured.

The validation setup correctly handles:

  • Multiple granularities for groupBy
  • Both string and array types for keyId and tag parameters
  • Proper timestamp coercion for start/end parameters

Also applies to: 82-89


464-490: LGTM! The identity mapping implementation is appropriate.

The individual cache lookups for identity mapping align with the caching strategy requirements.

@chronark chronark merged commit b7039df into main Jan 10, 2025
26 checks passed
@chronark chronark deleted the analytics-api branch January 10, 2025 13:39
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.

1 participant