import { mutation, query, internalMutation, internalQuery, } from "./_generated/server"; import { paginationOptsValidator } from "convex/server"; import { v } from "convex/values"; import * as Users from "./model/users"; export const store = mutation({ args: {}, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) throw new Error("Unauthenticated"); const existing = await ctx.db .query("users") .withIndex("by_external_id", (q) => q.eq("externalId", identity.subject), ) .unique(); if (existing) { if (existing.name !== identity.name) { await ctx.db.patch(existing._id, { name: identity.name ?? existing.name, }); } return existing._id; } return await ctx.db.insert("users", { name: identity.name ?? "Anonymous", email: identity.email ?? "", role: "customer", externalId: identity.subject, avatarUrl: identity.pictureUrl ?? undefined, }); }, }); export const current = query({ args: {}, handler: async (ctx) => Users.getCurrentUser(ctx), }); export const updateProfile = mutation({ args: { name: v.optional(v.string()), phone: v.optional(v.string()), avatarUrl: v.optional(v.string()), }, handler: async (ctx, args) => { const user = await Users.getCurrentUserOrThrow(ctx); const patch: { name?: string; phone?: string; avatarUrl?: string } = {}; if (args.name !== undefined) patch.name = args.name; if (args.phone !== undefined) patch.phone = args.phone; if (args.avatarUrl !== undefined) patch.avatarUrl = args.avatarUrl; if (Object.keys(patch).length === 0) return user._id; await ctx.db.patch(user._id, patch); return user._id; }, }); export const listCustomers = query({ args: { paginationOpts: paginationOptsValidator, }, handler: async (ctx, args) => { await Users.requireAdmin(ctx); return await ctx.db .query("users") .withIndex("by_role", (q) => q.eq("role", "customer")) .order("desc") .paginate(args.paginationOpts); }, }); export const upsertFromClerk = internalMutation({ args: { externalId: v.string(), name: v.string(), email: v.string(), avatarUrl: v.optional(v.string()), }, handler: async (ctx, args) => { const existing = await ctx.db .query("users") .withIndex("by_external_id", (q) => q.eq("externalId", args.externalId), ) .unique(); if (existing) { await ctx.db.patch(existing._id, { name: args.name, email: args.email, avatarUrl: args.avatarUrl, }); } else { await ctx.db.insert("users", { ...args, role: "customer", }); } }, }); export const deleteFromClerk = internalMutation({ args: { externalId: v.string() }, handler: async (ctx, { externalId }) => { const user = await ctx.db .query("users") .withIndex("by_external_id", (q) => q.eq("externalId", externalId)) .unique(); if (user) await ctx.db.delete(user._id); }, }); export const getById = internalQuery({ args: { userId: v.id("users") }, handler: async (ctx, args) => { const user = await ctx.db.get(args.userId); if (!user) throw new Error("User not found"); return user; }, }); export const setStripeCustomerId = internalMutation({ args: { userId: v.id("users"), stripeCustomerId: v.string(), }, handler: async (ctx, args) => { await ctx.db.patch(args.userId, { stripeCustomerId: args.stripeCustomerId, }); }, });