UNPKG

create-mf2-app

Version:

The stack AI moves fast with.

173 lines (148 loc) 4.65 kB
import { internalMutation, internalQuery, query, QueryCtx, action, internalAction, } from "../_generated/server"; import { v } from "convex/values"; import { Doc, Id } from "../_generated/dataModel"; import { createClerkClient, UserJSON } from "@clerk/backend"; import { api, internal } from "../_generated/api"; if (!process.env.CLERK_SECRET_KEY) { throw new Error("Missing CLERK_SECRET_KEY environment variable"); } const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY!, }); export const userLoginStatus = query( async ( ctx ): Promise< | ["No JWT Token", null] | ["No Clerk User", null] | ["Logged In", Doc<"users">] > => { const identity = await ctx.auth.getUserIdentity(); if (!identity) { return ["No JWT Token", null]; } const user = await getCurrentUser(ctx); if (user === null) { return ["No Clerk User", null]; } return ["Logged In", user]; } ); export const currentUser = query((ctx: QueryCtx) => getCurrentUser(ctx)); export const getUser = internalQuery({ args: { subject: v.string() }, async handler(ctx, args) { return await userQuery(ctx, args.subject); }, }); export const updateOrCreateUser = internalMutation({ args: { clerkUser: v.any() }, async handler(ctx, { clerkUser }: { clerkUser: UserJSON }) { const userRecord = await userQuery(ctx, clerkUser.id); if (userRecord === null) { await ctx.db.insert("users", { clerkUser }); const primaryEmail = clerkUser.email_addresses?.find( (email) => email.id === clerkUser.primary_email_address_id ); if (primaryEmail?.email_address) { try { await ctx.scheduler.runAfter(0, api.email.actions.sendWelcomeEmail, { to: primaryEmail.email_address, username: clerkUser.first_name || clerkUser.username || "User", }); } catch (error) { console.error("Failed to send welcome email:", error); } } } else { await ctx.db.patch(userRecord._id, { clerkUser }); } }, }); export const deleteUserByClerkId = internalMutation({ args: { id: v.string() }, async handler(ctx, { id }) { const userRecord = await userQuery(ctx, id); if (userRecord === null) { console.warn("can't delete user, does not exist", id); } else { await ctx.db.delete(userRecord._id); } }, }); export async function userQuery( ctx: QueryCtx, clerkUserId: string ): Promise<(Omit<Doc<"users">, "clerkUser"> & { clerkUser: UserJSON }) | null> { return await ctx.db .query("users") .withIndex("by_clerk_id", (q) => q.eq("clerkUser.id", clerkUserId)) .unique(); } export async function userById( ctx: QueryCtx, id: Id<"users"> ): Promise<(Omit<Doc<"users">, "clerkUser"> & { clerkUser: UserJSON }) | null> { return await ctx.db.get(id); } async function getCurrentUser(ctx: QueryCtx): Promise<Doc<"users"> | null> { const identity = await ctx.auth.getUserIdentity(); if (identity === null) { return null; } return await userQuery(ctx, identity.subject); } export async function mustGetCurrentUser(ctx: QueryCtx): Promise<Doc<"users">> { const userRecord = await getCurrentUser(ctx); if (!userRecord) throw new Error("Can't get current user"); return userRecord; } export const deleteUser = action({ args: {}, handler: async (ctx) => { const user = await ctx.runQuery(api.auth.users.currentUser); if (!user) { throw new Error("Can't get current user"); } await ctx.runAction(internal.auth.users.deleteClerkUser, { clerkUserId: user.clerkUser.id, }); await ctx.runMutation(internal.auth.users.deleteConvexUser, { userId: user._id, }); return { success: true }; }, }); export const deleteClerkUser = internalAction({ args: { clerkUserId: v.string() }, handler: async (_, { clerkUserId }) => { try { await clerkClient.users.deleteUser(clerkUserId); console.log("Successfully deleted user from Clerk:", clerkUserId); } catch (error) { console.error("Failed to delete user from Clerk:", error); throw new Error("Failed to delete user from Clerk"); } return { success: true }; }, }); export const deleteConvexUser = internalMutation({ args: { userId: v.id("users") }, handler: async (ctx, { userId }) => { const user = await ctx.db.get(userId); if (!user) { console.warn("User not found in Convex:", userId); return null; } await ctx.db.delete(userId); console.log("Successfully deleted user from Convex:", userId); return null; }, });