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

fix: upsert identity when creating keys in dashboard #2111

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

DeepaPrasanna
Copy link
Contributor

@DeepaPrasanna DeepaPrasanna commented Sep 18, 2024

What does this PR do?

Fixes #2098

Add identityId in keys table and upsert identity in identities table.

If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Add an owner id when creating key from the dashboard.
  • Check the keys table for the identityId and the identities table for the given owner id. It should be there now.

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

  • New Features

    • Introduced a new type declaration for enhanced database structure clarity.
    • Added functionality to associate keys with user identities during creation, improving auditing and access control.
    • Enhanced error handling during identity management in the key creation process.
  • Bug Fixes

    • Improved handling of duplicate keys when creating identities.

Copy link

changeset-bot bot commented Sep 18, 2024

⚠️ No Changeset found

Latest commit: b4f9273

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

vercel bot commented Sep 18, 2024

@DeepaPrasanna is attempting to deploy a commit to the Unkey Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Sep 18, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes introduced in this pull request include the addition of a new type declaration for the database in db.ts, enhancing type safety by defining a Database type based on PlanetScaleDatabase. Additionally, the createKey procedure in create.ts has been modified to handle an identity associated with the key. This involves checking for an ownerId, and if present, using the upsertIdentity function to link or create an identity in the database, thereby improving the handling of key creation with respect to user identities.

Changes

Files Change Summary
apps/dashboard/lib/db.ts Added a new exported type alias Database based on PlanetScaleDatabase type.
apps/dashboard/lib/trpc/routers/key/create.ts Enhanced createKey procedure to upsert an identity when an ownerId is provided. Added upsertIdentity function.

Assessment against linked issues

Objective Addressed Explanation
Creating keys with ownerId should upsert an identity. (Issue #2098)

Possibly related PRs

Suggested reviewers

  • chronark
  • mcstepp

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 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 Sep 18, 2024

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

@perkinsjr perkinsjr self-assigned this Sep 19, 2024
Copy link

vercel bot commented Sep 19, 2024

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

Name Status Preview Comments Updated (UTC)
dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 25, 2024 5:59pm
planetfall ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 25, 2024 5:59pm
play ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 25, 2024 5:59pm
workflows ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 25, 2024 5:59pm
www ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 25, 2024 5:59pm

apps/dashboard/lib/db.ts Show resolved Hide resolved
apps/dashboard/lib/trpc/routers/key/create.ts Outdated Show resolved Hide resolved
apps/dashboard/lib/trpc/routers/key/create.ts Outdated Show resolved Hide resolved
Comment on lines 149 to 156
let identity = await db.query.identities.findFirst({
where: (table, { and, eq }) =>
and(eq(table.workspaceId, workspaceId), eq(table.externalId, externalId)),
});
if (identity) {
return identity;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are you checking this and then returning it again.

You shouldn't need to check the identity and then if it exists return it, it is already there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry if I missed anything, but I thought we had to do an upsert here. As mentioned in the issue, I also referred to the code here

Copy link
Collaborator

@perkinsjr perkinsjr Oct 2, 2024

Choose a reason for hiding this comment

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

Sorry for the delay my notifications didn't ping, if you wanted to check for upserting. Why not do and early return the other way.

You current implementation just says query the db and set to variable identity, check if the identity exists and then return the identity, but you are then further down using the upsert functionality.

 let identity = await db.query.identities.findFirst({
    where: (table, { and, eq }) =>
      and(eq(table.workspaceId, workspaceId), eq(table.externalId, externalId)),
  });
  if (identity) {
    return identity;
  }

When really could do:

 let identity = await db.query.identities.findFirst({
    where: (table, { and, eq }) =>
      and(eq(table.workspaceId, workspaceId), eq(table.externalId, externalId)),
  });
  if (!identity) {
   // run inserts
  }

or

 let identity = await db.query.identities.findFirst({
    where: (table, { and, eq }) =>
      and(eq(table.workspaceId, workspaceId), eq(table.externalId, externalId)),
  });
 
 //handle the identity further down

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it! Understood what you meant. Thank you!

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

🧹 Outside diff range and nitpick comments (1)
apps/dashboard/lib/trpc/routers/key/create.ts (1)

144-195: Add unit tests for the upsertIdentity function

The upsertIdentity function plays a crucial role in ensuring that identities are correctly managed. Adding unit tests for this function will help catch any future bugs and ensure reliability.

Consider writing tests that cover:

  • Inserting a new identity when none exists.
  • Retrieving an existing identity without creating a new one.
  • Handling database errors gracefully.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between fd7369c and bece6ec.

📒 Files selected for processing (1)
  • apps/dashboard/lib/trpc/routers/key/create.ts (5 hunks)
🔇 Additional comments (1)
apps/dashboard/lib/trpc/routers/key/create.ts (1)

Line range hint 103-103: Reassess the necessity of both ownerId and identityId fields

In the key insertion, you are setting both ownerId and identityId:

  • ownerId: input.ownerId,
  • identityId: identity?.id,

Given that identityId now represents the link to the identity, and ownerId is used to create or find this identity, having both fields may be redundant. This could lead to confusion or inconsistency in data handling.

Check where ownerId is used in the codebase and determine if it can be deprecated in favor of identityId. Consolidating these fields might simplify the data model.

You can use the following script to find all references to ownerId:

Also applies to: 114-114

.insert(schema.identities)
.values({
id: newId("identity"),
createdAt: Date.now(),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use new Date() instead of Date.now() for consistent date objects

In your code, you're using Date.now() to set createdAt and updatedAt, which returns a timestamp number. However, elsewhere in your code, such as in line 96~, you use new Date(), which returns a Date object. This inconsistency can lead to issues if the database expects a Date object rather than a timestamp.

Apply this diff to fix the inconsistency:

-            createdAt: Date.now(),
+            createdAt: new Date(),

And in the onDuplicateKeyUpdate block:

-              updatedAt: Date.now(),
+              updatedAt: new Date(),

Also applies to: 168-168

Comment on lines +149 to +152
let identity = await db.query.identities.findFirst({
where: (table, { and, eq }) =>
and(eq(table.workspaceId, workspaceId), eq(table.externalId, externalId)),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize upsertIdentity to avoid unnecessary database queries

Currently, if the identity does not exist, you insert it and then perform another query to retrieve the identity. This results in two database calls. You can optimize this by using the result of the insert operation or by using an UPSERT that returns the updated or inserted row if your database supports it.

Modify the upsertIdentity function to return the identity directly from the insert operation:

     if (!identity) {
-      await db
+      identity = await db
         .insert(schema.identities)
         .values({
           id: newId("identity"),
-          createdAt: Date.now(),
+          createdAt: new Date(),
           environment: "default",
-          meta: {},
+          meta: JSON.stringify({}),
           externalId,
-          updatedAt: null,
+          updatedAt: null,
           workspaceId,
         })
         .onDuplicateKeyUpdate({
           set: {
-            updatedAt: Date.now(),
+            updatedAt: new Date(),
           },
         })
+        .returning()
+        .then((rows) => rows[0])
         .catch((_err) => {
           throw new TRPCError({
             code: "INTERNAL_SERVER_ERROR",
             message: "Failed to insert identity",
           });
         });

-      identity = await db.query.identities
-        .findFirst({
-          where: (table, { and, eq }) =>
-            and(
-              eq(table.workspaceId, workspaceId),
-              eq(table.externalId, externalId)
-            ),
-        })
-        .catch((_err) => {
-          throw new TRPCError({
-            code: "INTERNAL_SERVER_ERROR",
-            message: "Failed to read identity after upsert",
-          });
-        });
     }

This change eliminates the need for a second query, improving the efficiency of the function.

Also applies to: 178-185

id: newId("identity"),
createdAt: Date.now(),
environment: "default",
meta: {},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure consistent handling of the meta field

In the identities table insertion, meta is set to an empty object {}. In contrast, when inserting into the keys table (line 96~), meta is serialized using JSON.stringify. To maintain consistency and prevent potential issues with data storage and retrieval, ensure that meta is handled the same way in both cases.

Consider serializing meta when inserting into the identities table:

-            meta: {},
+            meta: JSON.stringify({}),

Or, if the database schema for identities.meta accepts JSON objects directly, ensure that this is consistently handled across your codebase.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
meta: {},
meta: JSON.stringify({}),

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

🧹 Outside diff range and nitpick comments (1)
apps/dashboard/lib/trpc/routers/key/create.ts (1)

80-83: LGTM: Identity upsert logic added

The addition of the identity variable with conditional upsertIdentity call effectively addresses the issue of linking or creating an identity when a key is created with an ownerId. This implementation aligns well with the PR objectives.

Consider using optional chaining for a more concise expression:

const identity = input.ownerId
  ? await upsertIdentity(db, keyAuth.workspaceId, input.ownerId)
  : null;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between bece6ec and b4f9273.

📒 Files selected for processing (1)
  • apps/dashboard/lib/trpc/routers/key/create.ts (4 hunks)
🔇 Additional comments (3)
apps/dashboard/lib/trpc/routers/key/create.ts (3)

1-1: LGTM: New imports added for Database and Identity types

The addition of Database and Identity type imports from "@/lib/db" is consistent with the new upsertIdentity function introduced in this file. This change enhances type safety and improves code readability.


114-114: LGTM: Identity linked to key in database

The addition of the identityId field in the key insertion, using identity?.id, effectively links the upserted identity to the newly created key in the database. This change fulfills the PR objective and ensures proper association between keys and identities.


Line range hint 1-192: Overall assessment: PR objectives achieved with minor improvements suggested

The changes made in this file successfully address the main objectives of the PR:

  1. An identityId field has been added to the keys table.
  2. An identity is now upserted when creating a key with an ownerId.
  3. The created/retrieved identity is properly linked to the newly created key.

These modifications effectively resolve the issue where creating a key with an ownerId did not link or create the corresponding identity in the database.

The implementation is generally sound, with only minor optimizations and consistency improvements suggested in the previous comments. Once these suggestions are addressed, the code will be more efficient and maintainable.

Great job on implementing this feature! The changes align well with the PR objectives and significantly improve the functionality of the key creation process.

Comment on lines +144 to +192
async function upsertIdentity(
db: Database,
workspaceId: string,
externalId: string,
): Promise<Identity> {
let identity = await db.query.identities.findFirst({
where: (table, { and, eq }) =>
and(eq(table.workspaceId, workspaceId), eq(table.externalId, externalId)),
});

if (!identity) {
await db
.insert(schema.identities)
.values({
id: newId("identity"),
createdAt: Date.now(),
environment: "default",
meta: {},
externalId,
updatedAt: null,
workspaceId,
})
.onDuplicateKeyUpdate({
set: {
updatedAt: Date.now(),
},
})
.catch((_err) => {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to insert identity",
});
});

identity = await db.query.identities
.findFirst({
where: (table, { and, eq }) =>
and(eq(table.workspaceId, workspaceId), eq(table.externalId, externalId)),
})
.catch((_err) => {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to read identity after upsert",
});
});
}

return identity as Identity;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Optimize upsertIdentity function and address inconsistencies

The new upsertIdentity function effectively handles the creation or retrieval of an identity. However, there are several areas for improvement:

  1. Reduce database queries: The function currently performs two separate database queries when inserting a new identity. Consider using a single upsert operation that returns the inserted/updated row.

  2. Improve error handling: Use more specific error codes and messages to aid in debugging and user feedback.

  3. Ensure consistency in date handling: Use new Date() instead of Date.now() for consistency with other parts of the codebase.

  4. Handle meta field consistently: Ensure the meta field is handled consistently across the codebase, either as a JSON string or an object.

Here's a suggested refactor:

async function upsertIdentity(
  db: Database,
  workspaceId: string,
  externalId: string,
): Promise<Identity> {
  try {
    const result = await db
      .insert(schema.identities)
      .values({
        id: newId("identity"),
        createdAt: new Date(),
        environment: "default",
        meta: JSON.stringify({}),
        externalId,
        updatedAt: null,
        workspaceId,
      })
      .onConflict((oc) =>
        oc.constraint('identities_workspace_id_external_id_unique').merge({
          updatedAt: new Date(),
        })
      )
      .returning()
      .execute();

    return result[0] as Identity;
  } catch (error) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Failed to upsert identity",
      cause: error,
    });
  }
}

This refactored version addresses the identified issues and improves the overall efficiency and consistency of the function.

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.

Creating keys in the dashboard with ownerId does not upsert an identity
2 participants