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
3 changes: 2 additions & 1 deletion apps/dashboard/lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { dbEnv } from "@/lib/env";
import { Client } from "@planetscale/database";
import { drizzle, schema } from "@unkey/db";
DeepaPrasanna marked this conversation as resolved.
Show resolved Hide resolved
import { type PlanetScaleDatabase, drizzle, schema } from "@unkey/db";
export type Database = PlanetScaleDatabase<typeof schema>;

export const db = drizzle(
new Client({
Expand Down
64 changes: 63 additions & 1 deletion apps/dashboard/lib/trpc/routers/key/create.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { db, schema } from "@/lib/db";
import { type Database, type Identity, db, schema } from "@/lib/db";
import { ingestAuditLogs } from "@/lib/tinybird";
import { rateLimitedProcedure, ratelimit } from "@/lib/trpc/ratelimitProcedure";
import { TRPCError } from "@trpc/server";
Expand Down Expand Up @@ -77,6 +77,10 @@ export const createKey = rateLimitedProcedure(ratelimit.create)
});
}

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

const keyId = newId("key");
const { key, hash, start } = await newKey({
prefix: input.prefix,
Expand Down Expand Up @@ -107,6 +111,7 @@ export const createKey = rateLimitedProcedure(ratelimit.create)
deletedAt: null,
enabled: input.enabled,
environment: input.environment,
identityId: identity?.id,
})
.catch((_err) => {
throw new TRPCError({
Expand Down Expand Up @@ -135,3 +140,60 @@ export const createKey = rateLimitedProcedure(ratelimit.create)

return { keyId, key };
});

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) {
Comment on lines +149 to +152
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

return identity;
}

await db
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!

.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",
});
});

DeepaPrasanna marked this conversation as resolved.
Show resolved Hide resolved
if (!identity) {
throw new TRPCError({
code: "NOT_FOUND",
message: "No identity present!",
});
}
return identity;
}
Loading